MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Zwróć tylko dopasowane elementy poddokumentu w zagnieżdżonej tablicy

Tak więc zapytanie, które masz, faktycznie wybiera „dokument”, tak jak powinien. Ale to, czego szukasz, to „filtrowanie zawartych w nich tablic”, tak aby zwrócone elementy pasowały tylko do warunku zapytania.

Prawdziwą odpowiedzią jest oczywiście to, że jeśli naprawdę nie oszczędzasz dużej przepustowości poprzez odfiltrowanie takich szczegółów, nie powinieneś nawet próbować, a przynajmniej poza pierwszym dopasowaniem pozycyjnym.

MongoDB ma pozycyjny $ operator, który zwróci element tablicy o dopasowanym indeksie z warunku zapytania. Jednak zwraca to tylko „pierwszy” dopasowany indeks „zewnętrznego” elementu tablicy.

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$': 1 }
)

W tym przypadku oznacza to "stores" tylko pozycja tablicy. Jeśli więc istnieje wiele wpisów „sklepy”, zwrócony zostanie tylko „jeden” z elementów, które zawierały dopasowany warunek. Ale , to nic nie robi dla wewnętrznej tablicy "offers" i jako taka każda „oferta” w dopasowanych "stores" tablica nadal byłaby zwracana.

MongoDB nie ma możliwości "filtrowania" tego w standardowym zapytaniu, więc nie działa:

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$.offers.$': 1 }
)

Jedynymi narzędziami, które MongoDB faktycznie musi wykonać na takim poziomie manipulacji, są ramy agregacji. Ale analiza powinna pokazać, dlaczego „prawdopodobnie” nie powinieneś tego robić, a zamiast tego po prostu filtrować tablicę w kodzie.

W kolejności, w jaki sposób możesz to osiągnąć według wersji.

Najpierw z MongoDB 3.2.x z użyciem $filter operacja:

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$project": {
    "stores": {
      "$filter": {
        "input": {
          "$map": {
            "input": "$stores",
            "as": "store",
            "in": {
              "_id": "$$store._id",
              "offers": {
                "$filter": {
                  "input": "$$store.offers",
                  "as": "offer",
                  "cond": {
                    "$setIsSubset":  [ ["L"], "$$offer.size" ]
                  }
                }
              }
            }
          }
        },
        "as": "store",
        "cond": { "$ne": [ "$$store.offers", [] ]}
      }
    }
  }}
])

Następnie z MongoDB 2.6.x i wyżej z $map i $setDifference :

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$project": {
    "stores": {
      "$setDifference": [
        { "$map": {
          "input": {
            "$map": {
              "input": "$stores",
              "as": "store",
              "in": {
                "_id": "$$store._id",
                "offers": {
                  "$setDifference": [
                    { "$map": {
                      "input": "$$store.offers",
                      "as": "offer",
                      "in": {
                        "$cond": {
                          "if": { "$setIsSubset": [ ["L"], "$$offer.size" ] },
                          "then": "$$offer",
                          "else": false
                        }
                      }
                    }},
                    [false]
                  ]
                }
              }
            }
          },
          "as": "store",
          "in": {
            "$cond": {
              "if": { "$ne": [ "$$store.offers", [] ] },
              "then": "$$store",
              "else": false
            }
          }
        }},
        [false]
      ]
    }
  }}
])

I wreszcie w dowolnej wersji powyżej MongoDB 2.2.x gdzie wprowadzono ramy agregacji.

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$unwind": "$stores" },
  { "$unwind": "$stores.offers" },
  { "$match": { "stores.offers.size": "L" } },
  { "$group": {
    "_id": {
      "_id": "$_id",
      "storeId": "$stores._id",
    },
    "offers": { "$push": "$stores.offers" }
  }},
  { "$group": {
    "_id": "$_id._id",
    "stores": {
      "$push": {
        "_id": "$_id.storeId",
        "offers": "$offers"
      }
    }
  }}
])

Rozłóżmy wyjaśnienia.

MongoDB 3.2.x i nowsze

Ogólnie rzecz biorąc, $filter to droga do tego, ponieważ została zaprojektowana z myślą o celu. Ponieważ istnieje wiele poziomów tablicy, należy to zastosować na każdym poziomie. Więc najpierw zagłębiasz się w każdą "offers" w "stores" do zbadania i $filter tę treść.

Oto proste porównanie:„Czy "size"? tablica zawiera element, którego szukam" . W tym logicznym kontekście, krótką rzeczą do zrobienia jest użycie $setIsSubset operacja porównania tablicy ("zestaw") ["L"] do tablicy docelowej. Gdzie ten warunek jest true ( zawiera "L" ), a następnie element tablicy dla "offers" jest zachowywany i zwracany w wyniku.

Na wyższym poziomie $filter , chcesz sprawdzić, czy wynik z poprzedniego $filter zwrócił pustą tablicę [] dla "offers" . Jeśli nie jest pusty, element jest zwracany lub w przeciwnym razie jest usuwany.

MongoDB 2.6.x

Jest to bardzo podobne do nowoczesnego procesu, z wyjątkiem tego, że nie ma $filter w tej wersji możesz użyć $map aby sprawdzić każdy element, a następnie użyć $setDifference aby odfiltrować wszystkie elementy, które zostały zwrócone jako false .

Więc $map zwróci całą tablicę, ale $cond operacja po prostu decyduje, czy zwrócić element, czy zamiast tego false wartość. W porównaniu $setDifference do pojedynczego elementu "zestaw" [false] wszystkie false elementy w zwróconej tablicy zostałyby usunięte.

Pod każdym innym względem logika jest taka sama jak powyżej.

MongoDB 2.2.x i nowsze

Tak więc poniżej MongoDB 2.6 jedynym narzędziem do pracy z tablicami jest $unwind , i tylko w tym celu nie w tym celu użyj struktury agregacji „tylko”.

Proces rzeczywiście wydaje się prosty, po prostu „rozkładając” każdą tablicę, odfiltrowując rzeczy, których nie potrzebujesz, a następnie składając je z powrotem. Główna opieka jest w "dwóch" $group etapy, przy czym „pierwszy” służy do odbudowy macierzy wewnętrznej, a następny do odbudowy macierzy zewnętrznej. Istnieją różne _id wartości na wszystkich poziomach, więc wystarczy je uwzględnić na każdym poziomie grupowania.

Ale problem polega na tym, że $unwind jest bardzo kosztowne . Chociaż nadal ma swój cel, głównym celem użycia nie jest wykonywanie tego rodzaju filtrowania na dokument. W rzeczywistości we współczesnych wydaniach należy go używać tylko wtedy, gdy element tablicy (tablic) musi stać się częścią samego „klucza grupującego”.

Wniosek

Zatem uzyskanie dopasowań na wielu poziomach tablicy takiej jak ta nie jest prostym procesem, a w rzeczywistości może być bardzo kosztowne jeśli zaimplementowane niepoprawnie.

W tym celu należy zawsze używać tylko dwóch nowoczesnych list, ponieważ wykorzystują one „pojedynczy” etap potoku oprócz „zapytania” $match w celu wykonania „filtrowania”. Wynikowy efekt jest trochę bardziej narzutowy niż standardowe formy .find() .

Ogólnie rzecz biorąc, te wykazy nadal są dość skomplikowane i jeśli naprawdę nie zmniejszysz drastycznie zawartości zwracanej przez takie filtrowanie w sposób, który znacznie poprawi przepustowość używaną między serwerem a klientem, to jesteś lepszy filtrowania wyników początkowego zapytania i podstawowej projekcji.

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$': 1 }
).forEach(function(doc) {
    // Technically this is only "one" store. So omit the projection
    // if you wanted more than "one" match
    doc.stores = doc.stores.filter(function(store) {
        store.offers = store.offers.filter(function(offer) {
            return offer.size.indexOf("L") != -1;
        });
        return store.offers.length != 0;
    });
    printjson(doc);
})

Tak więc praca z przetwarzaniem zapytania "post" zwróconego obiektu jest znacznie mniej uciążliwa niż używanie do tego potoku agregacji. Jak już wspomniano, jedyną „prawdziwą” różnicą jest to, że odrzucasz inne elementy na „serwerze” w przeciwieństwie do usuwania ich „dla każdego dokumentu” po otrzymaniu, co może zaoszczędzić trochę przepustowości.

Ale chyba że robisz to w nowoczesnym wydaniu z tylko $match i $project , wtedy „koszt” przetwarzania na serwerze znacznie przeważy „zysk” zmniejszenia narzutu sieci poprzez usunięcie najpierw niedopasowanych elementów.

We wszystkich przypadkach otrzymujesz ten sam wynik:

{
        "_id" : ObjectId("56f277b1279871c20b8b4567"),
        "stores" : [
                {
                        "_id" : ObjectId("56f277b5279871c20b8b4783"),
                        "offers" : [
                                {
                                        "_id" : ObjectId("56f277b1279871c20b8b4567"),
                                        "size" : [
                                                "S",
                                                "L",
                                                "XL"
                                        ]
                                }
                        ]
                }
        ]
}


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Czynniki do rozważenia przy wyborze MongoDB dla aplikacji Big Data

  2. Szyfrowanie danych MongoDB w spoczynku

  3. Wyposażony w szyfrowanie kopii zapasowych dla MySQL, MongoDB i PostgreSQL — ClusterControl 1.5.1

  4. Jak zaimplementować ten schemat w MongoDB?

  5. Zaktualizuj wiele pól w dokumencie MongoDB