$ lookup di ObjectId dalam sebuah array

103

Apa sintaks untuk melakukan $ lookup pada bidang yang merupakan larik ObjectIds daripada hanya satu ObjectId?

Contoh Dokumen Pemesanan:

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

Kueri Tidak Berfungsi:

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

Hasil yang diinginkan

{
  _id: ObjectId("..."),
  products: [
    ObjectId("..<Car ObjectId>.."),
    ObjectId("..<Bike ObjectId>..")
  ],
  productObjects: [
    {<Car Object>},
    {<Bike Object>}
  ],
}
Jason Lin
sumber
Apakah contoh saya dengan dokumen pesanan kurang jelas? Apakah Anda menginginkan dokumen contoh untuk produk?
Jason Lin
SERVER-22881 akan melacak pembuatan array bekerja seperti yang diharapkan (bukan sebagai nilai literal).
Asya Kamsky

Jawaban:

139

Pembaruan 2017

$ lookup sekarang bisa langsung menggunakan array sebagai field lokal . $unwindtidak lagi dibutuhkan.

Jawaban lama

Tahap $lookuppipeline agregasi tidak akan bekerja secara langsung dengan array. Maksud utama dari desain ini adalah untuk "gabungan kiri" sebagai jenis gabungan "satu ke banyak" (atau sebenarnya "pencarian") pada data terkait yang mungkin. Tetapi nilainya dimaksudkan untuk menjadi tunggal dan bukan sebuah array.

Oleh karena itu, Anda harus "membatalkan normalisasi" konten terlebih dahulu sebelum melakukan $lookupoperasi agar ini berfungsi. Dan itu berarti menggunakan $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" }
    }}
])

Setelah $lookupcocok setiap anggota array, hasilnya adalah array itu sendiri, jadi Anda $unwindkembali dan $groupke $pusharray baru untuk hasil akhir.

Perhatikan bahwa setiap kecocokan "left join" yang tidak ditemukan akan membuat array kosong untuk "productObjects" pada produk yang diberikan dan dengan demikian meniadakan dokumen untuk elemen "product" ketika yang kedua $unwinddipanggil.

Meskipun aplikasi langsung ke array akan bagus, hanya saja cara kerjanya saat ini dengan mencocokkan nilai singular ke banyak kemungkinan.

Seperti $lookuppada dasarnya sangat baru, saat ini bekerja seperti yang biasa bagi mereka yang akrab dengan luwak sebagai "versi orang miskin" dari .populate()metode yang ditawarkan di sana. Perbedaannya adalah bahwa $lookupmenawarkan "sisi server" pemrosesan "bergabung" sebagai lawan pada klien dan bahwa beberapa "kematangan" dalam $lookupsaat ini kurang dari apa yang .populate()ditawarkan (seperti interpolasi pencarian langsung pada array).

Ini sebenarnya adalah masalah yang ditugaskan untuk perbaikan SERVER-22881 , jadi dengan sedikit keberuntungan ini akan mengenai rilis berikutnya atau segera setelahnya.

Sebagai prinsip desain, struktur Anda saat ini tidak baik atau buruk, tetapi hanya dikenakan biaya tambahan saat membuat "sambungan" apa pun. Dengan demikian, prinsip berdiri dasar MongoDB di awal berlaku, di mana jika Anda "dapat" hidup dengan data "pra-bergabung" dalam satu koleksi, maka yang terbaik adalah melakukannya.

Satu hal lagi yang dapat dikatakan $lookupsebagai prinsip umum, adalah bahwa maksud dari "bergabung" di sini adalah untuk bekerja sebaliknya daripada yang ditunjukkan di sini. Jadi, daripada menyimpan "id terkait" dari dokumen lain di dalam dokumen "induk", prinsip umum yang bekerja paling baik adalah di mana "dokumen terkait" berisi referensi ke "induk".

Jadi $lookupdapat dikatakan "bekerja paling baik" dengan "desain relasi" yang merupakan kebalikan dari bagaimana sesuatu seperti luwak .populate()melakukan penggabungan sisi kliennya. Dengan idendifikasi "satu" dalam setiap "banyak", maka Anda cukup menarik item terkait tanpa perlu ke $unwindarray terlebih dahulu.

Blakes Seven
sumber
Terima kasih, ini berhasil! Apakah ini merupakan indikator bahwa data saya tidak terstruktur / dinormalisasi dengan benar?
Jason Lin
1
@JasonLin Tidak setegas "baik / buruk", jadi ada sedikit lebih banyak penjelasan yang ditambahkan ke jawaban. Itu tergantung pada apa yang cocok untuk Anda.
Blakes Seven
2
implementasi saat ini agak tidak disengaja. masuk akal untuk mencari semua nilai dalam larik bidang lokal, tidak masuk akal untuk menggunakan larik secara harfiah sehingga SERVER-22881 akan melacak untuk memperbaikinya.
Asya Kamsky
@AsyaKams Itu masuk akal. Saya biasanya telah memperlakukan pertanyaan $lookupdan validasi Dokumen sebagai fitur dalam masa pertumbuhan dan cenderung meningkat. Jadi, perluasan langsung pada larik akan disambut, seperti halnya "kueri" untuk memfilter hasil. Keduanya akan jauh lebih selaras dengan .populate()proses luwak yang biasa dilakukan banyak orang. Menambahkan tautan masalah langsung ke konten jawaban.
Blakes Seven
2
Perhatikan bahwa sesuai jawaban di bawah ini, ini sekarang telah diterapkan dan $lookupsekarang berfungsi langsung pada array.
Adam Reis
15

Anda juga bisa menggunakan pipelinedekor untuk melakukan pemeriksaan pada larik sub-dokumen

Berikut contoh penggunaan python(maaf saya orang ular).

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

Tangkapan di sini adalah untuk mencocokkan semua objek di ObjectId array(asing _idyang ada di localbidang / penyangga products).

Anda juga dapat membersihkan atau memproyeksikan catatan asing dengan tambahan stage, seperti yang ditunjukkan oleh komentar di atas.

pengguna12164
sumber
4

gunakan $ unwind Anda akan mendapatkan objek pertama, bukan array objek

pertanyaan:

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

hasil:

{
    "_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"
    }
}
KARTHIKEYAN.A
sumber
0

Menggabungkan dengan $lookupdan berikutnya $groupcukup rumit, jadi jika (dan itu media jika) Anda menggunakan node & Mongoose atau pustaka pendukung dengan beberapa petunjuk dalam skema, Anda dapat menggunakan a .populate()untuk mengambil dokumen-dokumen itu:

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")
    ...
Busur
sumber
0

Saya harus tidak setuju, kita dapat membuat $ lookup berfungsi dengan array ID jika kita mengawali dengan $ match stage.

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

Ini menjadi lebih rumit jika kita ingin meneruskan hasil pencarian ke pipeline. Tetapi sekali lagi ada cara untuk melakukannya (sudah disarankan oleh @ user12164):

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

Liebster Kamerad
sumber