2016-01-23 12 views
37

Qual è la sintassi per eseguire una ricerca $ su un campo che è una matrice di ObjectIds anziché un singolo ObjectId?

Esempio documento di ordine:

{ 
    _id: ObjectId("..."), 
    products: [ 
    ObjectId("..<Car ObjectId>.."), 
    ObjectId("..<Bike ObjectId>..") 
    ] 
} 

che non funziona Query:

db.orders.aggregate([ 
    { 
     $lookup: 
     { 
      from: "products", 
      localField: "products", 
      foreignField: "_id", 
      as: "productObjects" 
     } 
    } 
]) 

Risultato desiderato

{ 
    _id: ObjectId("..."), 
    products: [ 
    ObjectId("..<Car ObjectId>.."), 
    ObjectId("..<Bike ObjectId>..") 
    ], 
    productObjects: [ 
    {<Car Object>}, 
    {<Bike Object>} 
    ], 
} 
+0

È il mio esempio con il documento di ordine non abbastanza chiaro? vuoi documenti di esempio per i prodotti? –

+0

SERVER-22881 traccerà facendo in modo che l'array funzioni come previsto (non come valore letterale). –

risposta

63

La fase gasdotto $lookup aggregazione non lavorerà direttamente con un array. L'intento principale del progetto è per un "left join" come tipo "uno a molti" di join (o davvero una "ricerca") sui possibili dati correlati. Ma il valore è destinato a essere singolare e non un array.

Pertanto è necessario "de-normalizzare" il contenuto prima di eseguire l'operazione $lookup affinché funzioni. E questo significa che utilizzando $unwind:

db.orders.aggregate([ 
    // Unwind the source 
    { "$unwind": "$products" }, 
    // Do the lookup matching 
    { "$lookup": { 
     "from": "products", 
     "localField": "products", 
     "foreignField": "_id", 
     "as": "productObjects" 
    }}, 
    // Unwind the result arrays (likely one or none) 
    { "$unwind": "$productObjects" }, 
    // Group back to arrays 
    { "$group": { 
     "_id": "$_id", 
     "products": { "$push": "$products" }, 
     "productObjects": { "$push": "$productObjects" } 
    }} 
]) 

Dopo $lookup partite ogni membro della matrice il risultato è una matrice stessa, in modo da $unwind ancora e $group a $push nuovi array per il risultato finale.

Si noti che qualsiasi corrispondenza "left join" non trovata creerà una matrice vuota per "productObjects" sul prodotto specificato e quindi annullerà il documento per l'elemento "product" quando viene chiamato il secondo $unwind.

Anche se un'applicazione diretta a un array sarebbe piacevole, è proprio come questo attualmente funziona abbinando un valore singolare a un numero possibile.

Come $lookup è fondamentalmente molto nuovo, attualmente lavora come sarebbe familiare a coloro che hanno familiarità con mongoose come una "versione mans poveri" del metodo .populate() offerto lì. La differenza è che $lookup offre l'elaborazione "lato server" del "join" rispetto al client e che parte della "maturità" in $lookup non è attualmente disponibile rispetto alle offerte .populate() (come l'interpolazione della ricerca direttamente su un array) .

Questo è in realtà un problema assegnato per il miglioramento SERVER-22881, quindi con un po 'di fortuna questo avrebbe colpito la prossima versione o uno subito dopo.

Come principio di progettazione, la struttura corrente non è né buona né cattiva, ma solo soggetta a overheads durante la creazione di un "join". In quanto tale, si applica il principio base di MongoDB all'inizio, dove se "si può" convivere con i dati "pre-uniti" nell'unico insieme, allora è meglio farlo.

L'altra cosa che può essere detta di $lookup come principio generale, è che l'intento del "join" qui è quello di operare il contrario rispetto a quello mostrato qui. Quindi, piuttosto che mantenere gli "id correlati" degli altri documenti all'interno del documento "genitore", il principio generale che funziona meglio è dove i "documenti correlati" contengono un riferimento al "genitore".

Quindi $lookup si può dire che "funziona meglio" con un "design di relazione" che è il contrario di come qualcosa come mangusta .populate() esegue i suoi join lato client. Individuando invece "uno" all'interno di ciascun "molti", è sufficiente inserire gli elementi correlati senza dover prima usare l'array $unwind.

+0

Grazie, funziona! È un indicatore che i miei dati non sono strutturati/normalizzati correttamente? –

+1

@JasonLin Non così straigtforward come "buono/cattivo", quindi c'è un po 'più di spiegazione aggiunta alla risposta. Dipende da cosa ti si addice. –

+2

l'implementazione corrente è alquanto involontaria. ha senso cercare tutti i valori in una matrice di campo locale, non ha senso usare la matrice letteralmente in modo che SERVER-22881 tenga traccia di tale correzione. –

-1

aggregazione con $lookup e successiva $group è abbastanza ingombrante, quindi se (e questo è un mezzo di se) si sta utilizzando nodo & Mongoose o una libreria di supporto con qualche accenno nello schema, si potrebbe utilizzare un .populate() per recuperare tali documenti:

var mongoose = require("mongoose"), 
    Schema = mongoose.Schema; 

var productSchema = Schema({ ... }); 

var orderSchema = Schema({ 
    _id  : Number, 
    products: [ { type: Schema.Types.ObjectId, ref: "Product" } ] 
}); 

var Product = mongoose.model("Product", productSchema); 
var Order = mongoose.model("Order", orderSchema); 

... 

Order 
    .find(...) 
    .populate("products") 
    ... 
1

uso $ rilassarsi si otterrà il primo oggetto, invece di array di oggetti

query:

db.getCollection('vehicles').aggregate([ 
    { 
    $match: { 
     status: "AVAILABLE", 
     vehicleTypeId: { 
     $in: Array.from(newSet(d.vehicleTypeIds)) 
     } 
    } 
    }, 
    { 
    $lookup: { 
     from: "servicelocations", 
     localField: "locationId", 
     foreignField: "serviceLocationId", 
     as: "locations" 
    } 
    }, 
    { 
    $unwind: "$locations" 
    } 
]); 

risultato:

{ 
    "_id" : ObjectId("59c3983a647101ec58ddcf90"), 
    "vehicleId" : "45680", 
    "regionId" : 1.0, 
    "vehicleTypeId" : "10TONBOX", 
    "locationId" : "100", 
    "description" : "Isuzu/2003-10 Ton/Box", 
    "deviceId" : "", 
    "earliestStart" : 36000.0, 
    "latestArrival" : 54000.0, 
    "status" : "AVAILABLE", 
    "accountId" : 1.0, 
    "locations" : { 
     "_id" : ObjectId("59c3afeab7799c90ebb3291f"), 
     "serviceLocationId" : "100", 
     "regionId" : 1.0, 
     "zoneId" : "DXBZONE1", 
     "description" : "Masafi Park Al Quoz", 
     "locationPriority" : 1.0, 
     "accountTypeId" : 0.0, 
     "locationType" : "DEPOT", 
     "location" : { 
      "makani" : "", 
      "lat" : 25.123091, 
      "lng" : 55.21082 
     }, 
     "deliveryDays" : "MTWRFSU", 
     "timeWindow" : { 
      "timeWindowTypeId" : "1" 
     }, 
     "address1" : "", 
     "address2" : "", 
     "phone" : "", 
     "city" : "", 
     "county" : "", 
     "state" : "", 
     "country" : "", 
     "zipcode" : "", 
     "imageUrl" : "", 
     "contact" : { 
      "name" : "", 
      "email" : "" 
     }, 
     "status" : "", 
     "createdBy" : "", 
     "updatedBy" : "", 
     "updateDate" : "", 
     "accountId" : 1.0, 
     "serviceTimeTypeId" : "1" 
    } 
} 


{ 
    "_id" : ObjectId("59c3983a647101ec58ddcf91"), 
    "vehicleId" : "81765", 
    "regionId" : 1.0, 
    "vehicleTypeId" : "10TONBOX", 
    "locationId" : "100", 
    "description" : "Hino/2004-10 Ton/Box", 
    "deviceId" : "", 
    "earliestStart" : 36000.0, 
    "latestArrival" : 54000.0, 
    "status" : "AVAILABLE", 
    "accountId" : 1.0, 
    "locations" : { 
     "_id" : ObjectId("59c3afeab7799c90ebb3291f"), 
     "serviceLocationId" : "100", 
     "regionId" : 1.0, 
     "zoneId" : "DXBZONE1", 
     "description" : "Masafi Park Al Quoz", 
     "locationPriority" : 1.0, 
     "accountTypeId" : 0.0, 
     "locationType" : "DEPOT", 
     "location" : { 
      "makani" : "", 
      "lat" : 25.123091, 
      "lng" : 55.21082 
     }, 
     "deliveryDays" : "MTWRFSU", 
     "timeWindow" : { 
      "timeWindowTypeId" : "1" 
     }, 
     "address1" : "", 
     "address2" : "", 
     "phone" : "", 
     "city" : "", 
     "county" : "", 
     "state" : "", 
     "country" : "", 
     "zipcode" : "", 
     "imageUrl" : "", 
     "contact" : { 
      "name" : "", 
      "email" : "" 
     }, 
     "status" : "", 
     "createdBy" : "", 
     "updatedBy" : "", 
     "updateDate" : "", 
     "accountId" : 1.0, 
     "serviceTimeTypeId" : "1" 
    } 
}