Bir dizideki ObjectId'ler için $ arama


104

Tek bir ObjectId yerine bir ObjectIds dizisi olan bir alanda $ araması yapmanın sözdizimi nedir?

Örnek Sipariş Belgesi:

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

Sorgu Çalışmıyor:

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

İstenen sonuç

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

Sipariş belgeli örneğim yeterince açık değil mi? ürünler için örnek belgeler ister misiniz?
Jason Lin

SERVER-22881, dizinin beklendiği gibi çalışmasını izler (değişmez değer olarak değil).
Asya Kamsky

Yanıtlar:


145

2017 güncellemesi

$ lookup artık yerel alan olarak doğrudan bir diziyi kullanabilir . $unwindartık gerekli değil.

Eski cevap

$lookupToplama boru hattı aşaması bir dizi doğrudan çalışmayacaktır. Tasarımın ana amacı, olası ilgili veriler üzerinde "bire çok" birleştirme türü (veya gerçekten bir "arama") olarak "sol birleştirme" içindir. Ancak değerin bir dizi değil tekil olması amaçlanmıştır.

Bu nedenle, bunun $lookupçalışması için işlemi gerçekleştirmeden önce içeriği "normalleştirmeniz" gerekir . Ve bu şu anlama gelir $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" }
    }}
])

Sonra $lookupkibrit her dizi elemanı sonucu böylece, bir dizi kendisidir $unwindve tekrar $groupetmek $pushnihai sonuç için yeni diziler.

Bulunmayan herhangi bir "sol birleşim" eşleşmesinin, verilen üründeki "productObjects" için boş bir dizi oluşturacağını ve böylece ikinci $unwindçağrıldığında "product" öğesi için belgeyi geçersiz kılacağını unutmayın .

Bir diziye doğrudan uygulama güzel olsa da, tekil bir değeri olası bir çok sayı ile eşleştirerek şu anda bunun nasıl çalıştığıdır.

$lookupTemelde çok yeni olduğu gibi , şu anda firavun faresini.populate() orada sunulan yöntemin "fakir bir versiyonu" olarak tanıyanların bildiği gibi çalışıyor. Aradaki fark $lookup, istemcideki yerine "birleştirmenin" "sunucu tarafında" işlenmesini sunması ve "olgunluğun" bir kısmının $lookupşu anda hangi .populate()tekliflerden yoksun olmasıdır (aramayı doğrudan bir dizide ara değerleme gibi).

Bu aslında SERVER-22881'i iyileştirmek için atanmış bir sorundur , bu yüzden biraz şansla bu bir sonraki sürümde veya hemen sonra olur.

Bir tasarım ilkesi olarak, mevcut yapınız ne iyi ne de kötüdür, ancak herhangi bir "birleştirme" oluştururken genel giderlere tabidir. Bu nedenle, MongoDB'nin başlangıçtaki temel kalıcı ilkesi geçerlidir; burada, bir koleksiyonda "önceden birleştirilmiş" verilerle "yaşayabilir "seniz, bunu yapmak en iyisidir.

$lookupGenel bir ilke olarak söylenebilecek diğer bir şey de, buradaki "birleştirme" nin amacının burada gösterilenden farklı bir yolla çalışmak olmasıdır. Bu nedenle, diğer belgelerin "ilgili kimliklerini" "ana" belge içinde tutmak yerine, en iyi sonuç veren genel ilke, "ilgili belgelerin" "ana" belgeye bir atıf içermesidir.

Bu nedenle $lookup, firavun faresi gibi bir şeyin .populate()müşteri tarafı birleşimlerini nasıl gerçekleştirdiğinin tersi olan bir "ilişki tasarımı" ile "en iyi şekilde çalışın" denilebilir . Bunun yerine her "çok" içindeki "bir" i tanımlayarak, $unwindönce diziye ihtiyaç duymadan ilgili öğeleri çekersiniz .


Teşekkür ederim işe yarıyor! Bu, verilerimin düzgün yapılandırılmadığının / normalleştirilmediğinin bir göstergesi mi?
Jason Lin

1
@JasonLin "İyi / kötü" kadar katı değil, bu yüzden cevaba biraz daha açıklama eklendi. Size neyin uygun olduğuna bağlı.
Blakes Seven

2
mevcut uygulama bir şekilde kasıtsızdır. bir yerel alan dizisindeki tüm değerleri aramak mantıklıdır, diziyi kelimenin tam anlamıyla kullanmak mantıklı değildir, bu nedenle SERVER-22881 bunu düzeltmeyi izleyecektir.
Asya Kamsky

@AsyaKamsky Bu mantıklı. Genelde araştırma $lookupve Belge doğrulamasını emekleme dönemindeki özellikler olarak ele alıyorum ve muhtemelen gelişecek. Dolayısıyla, sonuçları filtrelemek için bir "sorgu" gibi bir dizide doğrudan genişleme memnuniyetle karşılanacaktır. Her ikisi de, .populate()çoğu kişinin alıştığı firavun faresi süreciyle çok daha uyumlu olacaktır. Sorun bağlantısını doğrudan cevap içeriğine eklemek.
Blakes Seven

2
Aşağıdaki cevaba göre, bunun şimdi uygulandığını ve $lookupartık doğrudan bir dizi üzerinde çalıştığını unutmayın.
Adam Reis


15

pipelineSahneyi, bir alt belge dizisi üzerinde kontroller yapmak için de kullanabilirsiniz.

İşte örnek python(üzgünüm ben yılan gibiyim).

db.products.aggregate([
  { '$lookup': {
      'from': 'products',
      'let': { 'pid': '$products' },
      'pipeline': [
        { '$match': { '$expr': { '$in': ['$_id', '$$pid'] } } }
        // Add additional stages here 
      ],
      'as':'productObjects'
  }
])

Buradaki yakalama, ObjectId array( alan / pervane _idiçindeki yabancı) içindeki tüm nesneleri eşleştirmektir .localproducts

Ayrıca stage, yukarıdaki yorumda belirtildiği gibi , yabancı kayıtları ek s ile temizleyebilir veya yansıtabilirsiniz .


4

$ çözmeyi kullanın , nesneler dizisi yerine ilk nesneyi alırsınız

sorgu:

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"
  }
]);

sonuç:

{
    "_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"
    }
}

0

İle toplayarak $lookupve müteakip $groupeğer öyleyse, oldukça zahmetlidir (ve eğer orta olduğunu) Eğer düğüm & Mongoose veya şemasındaki bazı ipuçlarıyla destekleyici kütüphane kullanıyorsanız, bir kullanabilirsiniz .populate()bu belgeleri getirmesi:

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")
    ...

0

Buna katılmıyorum, $ match aşamasıyla önsöz verirsek $ aramayı IDs dizisi ile çalıştırabiliriz.

// replace IDs array with lookup results
db.products.aggregate([
    { $match: { products : { $exists: true } } },
    {
        $lookup: {
            from: "products",
            localField: "products",
            foreignField: "_id",
            as: "productObjects"
        }
    }
])

Arama sonucunu bir ardışık düzene geçirmek istersek daha karmaşık hale gelir. Ama yine de bunu yapmanın bir yolu var (@ user12164 tarafından zaten önerildi):

// replace IDs array with lookup results passed to pipeline
db.products.aggregate([
    { $match: { products : { $exists: true } } },
    {
        $lookup: {
            from: "products",
             let: { products: "$products"},
             pipeline: [
                 { $match: { $expr: {$in: ["$_id", "$$products"] } } },
                 { $project: {_id: 0} } // suppress _id
             ],
            as: "productObjects"
        }
    }
])

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.