Pobierz tylko żądany element z tablicy obiektów w kolekcji MongoDB

377

Załóżmy, że masz w mojej kolekcji następujące dokumenty:

{  
   "_id":ObjectId("562e7c594c12942f08fe4192"),
   "shapes":[  
      {  
         "shape":"square",
         "color":"blue"
      },
      {  
         "shape":"circle",
         "color":"red"
      }
   ]
},
{  
   "_id":ObjectId("562e7c594c12942f08fe4193"),
   "shapes":[  
      {  
         "shape":"square",
         "color":"black"
      },
      {  
         "shape":"circle",
         "color":"green"
      }
   ]
}

Wykonaj zapytanie:

db.test.find({"shapes.color": "red"}, {"shapes.color": 1})

Lub

db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1})

Zwraca dopasowany dokument (Dokument 1) , ale zawsze ze WSZYSTKIMMI elementami tablicy w shapes:

{ "shapes": 
  [
    {"shape": "square", "color": "blue"},
    {"shape": "circle", "color": "red"}
  ] 
}

Chciałbym jednak uzyskać dokument (Dokument 1) tylko z tablicą zawierającą color=red:

{ "shapes": 
  [
    {"shape": "circle", "color": "red"}
  ] 
}

W jaki sposób mogę to zrobić?

Sebtm
źródło

Odpowiedzi:

416

Nowy $elemMatchoperator rzutowania w MongoDB 2.2 zapewnia inny sposób zmiany zwracanego dokumentu, tak aby zawierał tylko pierwszy dopasowany shapeselement:

db.test.find(
    {"shapes.color": "red"}, 
    {_id: 0, shapes: {$elemMatch: {color: "red"}}});

Zwroty:

{"shapes" : [{"shape": "circle", "color": "red"}]}

W 2.2 można to również zrobić za pomocą $ projection operator, gdzie $nazwa w polu obiektu rzutowania reprezentuje indeks pierwszego pasującego elementu tablicy z zapytania. Poniższe zwraca takie same wyniki jak powyżej:

db.test.find({"shapes.color": "red"}, {_id: 0, 'shapes.$': 1});

Aktualizacja MongoDB 3.2

Począwszy od wersji 3.2, można użyć nowego $filteroperatora agregacji do filtrowania tablicy podczas projekcji, co ma tę zaletę, że obejmuje wszystkie dopasowania, a nie tylko pierwszą.

db.test.aggregate([
    // Get just the docs that contain a shapes element where color is 'red'
    {$match: {'shapes.color': 'red'}},
    {$project: {
        shapes: {$filter: {
            input: '$shapes',
            as: 'shape',
            cond: {$eq: ['$$shape.color', 'red']}
        }},
        _id: 0
    }}
])

Wyniki:

[ 
    {
        "shapes" : [ 
            {
                "shape" : "circle",
                "color" : "red"
            }
        ]
    }
]
JohnnyHK
źródło
15
jakieś rozwiązanie, jeśli chcę, aby zwracało wszystkie pasujące elementy zamiast tylko pierwszego?
Steve Ng,
Obawiam się, że używam Mongo 3.0.X :-(
charliebrownie
@charliebrownie Następnie użyj jednej z pozostałych odpowiedzi aggregate.
JohnnyHK,
to zapytanie zwraca tylko „kształty” tablicy i nie zwraca innych pól. Czy ktoś wie, jak zwrócić także inne pola?
Mark Thien
1
Działa to również:db.test.find({}, {shapes: {$elemMatch: {color: "red"}}});
Paul
97

Nowa struktura agregacji w MongoDB 2.2+ stanowi alternatywę dla Map / Reduce. Za pomocą $unwindoperatora można podzielić shapestablicę na strumień dokumentów, które można dopasować:

db.test.aggregate(
  // Start with a $match pipeline which can take advantage of an index and limit documents processed
  { $match : {
     "shapes.color": "red"
  }},
  { $unwind : "$shapes" },
  { $match : {
     "shapes.color": "red"
  }}
)

Prowadzi do:

{
    "result" : [
        {
            "_id" : ObjectId("504425059b7c9fa7ec92beec"),
            "shapes" : {
                "shape" : "circle",
                "color" : "red"
            }
        }
    ],
    "ok" : 1
}
Stennie
źródło
6
@JohnnyHK: W tym przypadku $elemMatchjest inna opcja. Dostałem się tutaj przez pytanie z Google Group, w którym $ elemMatch nie działałoby, ponieważ zwraca tylko pierwsze dopasowanie dla dokumentu.
Stennie,
1
Dzięki, nie byłem świadomy tego ograniczenia, więc dobrze wiedzieć. Przepraszam za usunięcie mojego komentarza, na który odpowiadasz, postanowiłem zamiast tego opublikować inną odpowiedź i nie chciałem mylić ludzi.
JohnnyHK,
3
@JohnnyHK: Nie martw się, są teraz trzy przydatne odpowiedzi na pytanie ;-)
Stennie,
W przypadku innych wyszukiwarek oprócz tego próbowałem również dodać { $project : { shapes : 1 } }- co wydawało się działać i byłoby pomocne, gdyby załączone dokumenty były duże, a chciałeś tylko zobaczyć shapeskluczowe wartości.
user1063287,
2
@calmbird Zaktualizowałem przykład, aby uwzględnić początkowy etap dopasowania $. Jeśli jesteś zainteresowany bardziej wydajną sugestią dotyczącą funkcji, chciałbym obejrzeć / zaktualizować SERVER-6612: Wsparcie w projektowaniu wielu wartości tablic w projekcji, takiej jak specyfikator projekcji $ elemMatch w narzędziu do śledzenia problemów MongoDB.
Stennie
30

Innym interesującym sposobem jest użycie $ redact , która jest jedną z nowych funkcji agregujących MongoDB 2.6 . Jeśli używasz wersji 2.6, nie potrzebujesz $ odprężenia, co może powodować problemy z wydajnością, jeśli masz duże tablice.

db.test.aggregate([
    { $match: { 
         shapes: { $elemMatch: {color: "red"} } 
    }},
    { $redact : {
         $cond: {
             if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]},
             then: "$$DESCEND",
             else: "$$PRUNE"
         }
    }}]);

$redact „ogranicza zawartość dokumentów na podstawie informacji przechowywanych w samych dokumentach” . Będzie więc działał tylko w dokumencie . Zasadniczo skanuje dokument od góry do dołu i sprawdza, czy pasuje do ifstanu, w którym się znajduje $cond, jeśli jest zgodny, zachowa zawartość ( $$DESCEND) lub usunie ( $$PRUNE).

W powyższym przykładzie najpierw $matchzwraca całą shapestablicę, a $ redact usuwa ją do oczekiwanego wyniku.

Zauważ, że {$not:"$color"}jest to konieczne, ponieważ skanuje również najwyższy dokument, a jeśli $redactnie znajdzie colorpola na najwyższym poziomie, to zwróci to, falseco może pozbawić cały dokument, którego nie chcemy.

anvarik
źródło
1
idealna odpowiedź. Jak wspomniałeś, $indind zużywa dużo pamięci RAM. Tak będzie lepiej w porównaniu.
manojpt
Mam wątpliwości. W tym przykładzie „kształty” to tablica. Czy „$ redact” przeskanuje wszystkie obiekty w tablicy „kształtów”? Jak to będzie dobre pod względem wydajności?
manojpt
nie wszystko, ale wynik twojego pierwszego meczu. To jest powód, dla którego $match
stawiasz
okkk .. jeśli indeks utworzony w polu „kolor”, nawet wtedy skanuje wszystkie obiekty w tablicy „kształtów” ??? Jaki może być skuteczny sposób dopasowywania wielu obiektów w tablicy?
manojpt
2
Znakomity! Nie rozumiem, jak działa tutaj eq $. Zostawiłem to początkowo i to nie działało dla mnie. Jakoś szuka w tablicy kształtów, aby znaleźć dopasowanie, ale zapytanie nigdy nie określa, w której tablicy należy szukać. Na przykład, jeśli dokumenty miały kształty i, na przykład, rozmiary; czy $ eq szukałby w obu tablicach dopasowań? Czy $ redact szuka tylko w dokumencie czegoś, co pasuje do warunku „jeśli”?
Onosa
30

Uwaga: ta odpowiedź stanowi rozwiązanie, które było istotne w tym czasie , zanim wprowadzono nowe funkcje MongoDB 2.2 i nowsze. Zobacz inne odpowiedzi, jeśli korzystasz z nowszej wersji MongoDB.

Parametr wyboru pola jest ograniczony do pełnych właściwości. Nie można go użyć do wybrania części tablicy, tylko całej tablicy. Próbowałem użyć operatora $ pozycyjnego , ale to nie działało.

Najłatwiej jest po prostu filtrować kształty w kliencie .

Jeśli naprawdę potrzebujesz poprawnych danych wyjściowych bezpośrednio z MongoDB, możesz użyć mapowania do filtrowania kształtów.

function map() {
  filteredShapes = [];

  this.shapes.forEach(function (s) {
    if (s.color === "red") {
      filteredShapes.push(s);
    }
  });

  emit(this._id, { shapes: filteredShapes });
}

function reduce(key, values) {
  return values[0];
}

res = db.test.mapReduce(map, reduce, { query: { "shapes.color": "red" } })

db[res.result].find()
Niels van der Rest
źródło
24

Lepiej możesz zapytać w dopasowanym elemencie tablicy, $sliceczy przydatne jest zwrócenie znaczącego obiektu w tablicy.

db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})

$slicejest przydatny, gdy znasz indeks elementu, ale czasami chcesz, który element tablicy spełni twoje kryteria. Możesz zwrócić pasujący element za pomocą $operatora.

Narendran
źródło
19
 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

WYJŚCIA

{

   "shapes" : [ 
       {
           "shape" : "circle",
           "color" : "red"
       }
   ]
}
Wirusowy Patel
źródło
12

Składnia find w mongodb jest następująca

    db.<collection name>.find(query, projection);

i drugie zapytanie, które napisałeś

    db.test.find(
    {shapes: {"$elemMatch": {color: "red"}}}, 
    {"shapes.color":1})

w tym użyłeś $elemMatchoperatora w części zapytania, natomiast jeśli użyjesz tego operatora w części projekcji, otrzymasz pożądany wynik. Możesz zapisać swoje zapytanie jako

     db.users.find(
     {"shapes.color":"red"},
     {_id:0, shapes: {$elemMatch : {color: "red"}}})

To da pożądany rezultat.

Vicky
źródło
1
To działa dla mnie. Wydaje się jednak, że "shapes.color":"red"w parametrze zapytania (pierwszy parametr metody find) nie jest konieczne. Możesz go zastąpić {}i uzyskać te same wyniki.
Erik Olson,
2
@ErikOlson Twoja sugestia jest słuszna w powyższym przypadku, w którym musimy znaleźć cały dokument w kolorze czerwonym i zastosować tylko projekcję. Powiedzmy jednak, że jeśli ktoś chce znaleźć cały dokument, który ma kolor niebieski, powinien zwrócić tylko te elementy tablicy kształtów, które mają kolor czerwony. W tym przypadku do powyższego zapytania może odwoływać się także ktoś inny.
Vicky
To wydaje się najłatwiejsze, ale nie mogę sprawić, żeby działało. Zwraca tylko pierwszy zgodny dokument podrzędny.
newman
8

Dzięki JohnnyHK .

Tutaj chcę tylko dodać bardziej złożone użycie.

// Document 
{ 
"_id" : 1
"shapes" : [
  {"shape" : "square",  "color" : "red"},
  {"shape" : "circle",  "color" : "green"}
  ] 
} 

{ 
"_id" : 2
"shapes" : [
  {"shape" : "square",  "color" : "red"},
  {"shape" : "circle",  "color" : "green"}
  ] 
} 


// The Query   
db.contents.find({
    "_id" : ObjectId(1),
    "shapes.color":"red"
},{
    "_id": 0,
    "shapes" :{
       "$elemMatch":{
           "color" : "red"
       } 
    }
}) 


//And the Result

{"shapes":[
    {
       "shape" : "square",
       "color" : "red"
    }
]}
Wir
źródło
7

Musisz tylko uruchomić zapytanie

db.test.find(
{"shapes.color": "red"}, 
{shapes: {$elemMatch: {color: "red"}}});

wyjście tego zapytania to

{
    "_id" : ObjectId("562e7c594c12942f08fe4192"),
    "shapes" : [ 
        {"shape" : "circle", "color" : "red"}
    ]
}

zgodnie z oczekiwaniami, poda dokładne pole z tablicy, które pasuje do koloru: „czerwony”.

Vaibhav Patil
źródło
3

wraz z projektem $ bardziej odpowiednie będą inne mądre elementy pasujące do siebie razem z innymi elementami w dokumencie.

db.test.aggregate(
  { "$unwind" : "$shapes" },
  { "$match" : {
     "shapes.color": "red"
  }},
{"$project":{
"_id":1,
"item":1
}}
)
shakthydoss
źródło
czy możesz opisać, że osiąga się to dzięki zestawowi danych wejściowych i wyjściowych?
Alexander Mills,
2

Podobnie można znaleźć dla wielokrotności

db.getCollection('localData').aggregate([
    // Get just the docs that contain a shapes element where color is 'red'
  {$match: {'shapes.color': {$in : ['red','yellow'] } }},
  {$project: {
     shapes: {$filter: {
        input: '$shapes',
        as: 'shape',
        cond: {$in: ['$$shape.color', ['red', 'yellow']]}
     }}
  }}
])
ashishSober
źródło
Ta odpowiedź jest rzeczywiście preferowanym sposobem 4.x: $matchzmniejszyć przestrzeń, a następnie $filterzachować to, co chcesz, nadpisując pole wejściowe (użyj danych wyjściowych $filterpola on, shapesaby $projectwrócić do shapes. Uwaga dotycząca stylu: najlepiej nie używać nazwy pola jako asargumentem, ponieważ może to prowadzić do nieporozumień później $$shapei $shapewolę. zzjako aspola, ponieważ tak naprawdę wyróżnia.
Buzz Moschetti
1
db.test.find( {"shapes.color": "red"}, {_id: 0})
Poonam Agrawal
źródło
1
Witamy w Stack Overflow! Dziękujemy za fragment kodu, który może zapewnić pewną ograniczoną, natychmiastową pomoc. Właściwe wyjaśnienie znacznie poprawiłoby jego długoterminową wartość , opisując, dlaczego jest to dobre rozwiązanie problemu i uczyniłoby to bardziej użytecznym dla przyszłych czytelników z innymi podobnymi pytaniami. Edytuj swoją odpowiedź, aby dodać wyjaśnienie, w tym przyjęte założenia.
wrzesień
1

Użyj funkcji agregacji i $projectuzyskaj określone pole obiektu w dokumencie

db.getCollection('geolocations').aggregate([ { $project : { geolocation : 1} } ])

wynik:

{
    "_id" : ObjectId("5e3ee15968879c0d5942464b"),
    "geolocation" : [ 
        {
            "_id" : ObjectId("5e3ee3ee68879c0d5942465e"),
            "latitude" : 12.9718313,
            "longitude" : 77.593551,
            "country" : "India",
            "city" : "Chennai",
            "zipcode" : "560001",
            "streetName" : "Sidney Road",
            "countryCode" : "in",
            "ip" : "116.75.115.248",
            "date" : ISODate("2020-02-08T16:38:06.584Z")
        }
    ]
}
KARTHIKEYAN.A
źródło
0

Chociaż pytanie zostało zadane 9,6 lat temu, było to ogromną pomocą dla wielu osób, a ja jestem jedną z nich. Dziękujemy wszystkim za wszystkie pytania, wskazówki i odpowiedzi. Pobranie jednej z odpowiedzi tutaj. Odkryłem, że poniższej metody można również użyć do wyświetlenia innych pól w dokumencie nadrzędnym. Może to być pomocne dla kogoś.

W przypadku poniższego dokumentu konieczne było sprawdzenie, czy pracownik (emp # 7839) ma ustawioną historię urlopów na rok 2020. Historia urlopów jest implementowana jako dokument osadzony w nadrzędnym dokumencie pracownika.

db.employees.find( {"leave_history.calendar_year": 2020}, 
    {leave_history: {$elemMatch: {calendar_year: 2020}},empno:true,ename:true}).pretty()


{
        "_id" : ObjectId("5e907ad23997181dde06e8fc"),
        "empno" : 7839,
        "ename" : "KING",
        "mgrno" : 0,
        "hiredate" : "1990-05-09",
        "sal" : 100000,
        "deptno" : {
                "_id" : ObjectId("5e9065f53997181dde06e8f8")
        },
        "username" : "none",
        "password" : "none",
        "is_admin" : "N",
        "is_approver" : "Y",
        "is_manager" : "Y",
        "user_role" : "AP",
        "admin_approval_received" : "Y",
        "active" : "Y",
        "created_date" : "2020-04-10",
        "updated_date" : "2020-04-10",
        "application_usage_log" : [
                {
                        "logged_in_as" : "AP",
                        "log_in_date" : "2020-04-10"
                },
                {
                        "logged_in_as" : "EM",
                        "log_in_date" : ISODate("2020-04-16T07:28:11.959Z")
                }
        ],
        "leave_history" : [
                {
                        "calendar_year" : 2020,
                        "pl_used" : 0,
                        "cl_used" : 0,
                        "sl_used" : 0
                },
                {
                        "calendar_year" : 2021,
                        "pl_used" : 0,
                        "cl_used" : 0,
                        "sl_used" : 0
                }
        ]
}
Ali
źródło