Подтвердить что ты не робот

$ lookup для ObjectId в массиве

Какой синтаксис для выполнения $lookup в поле, которое является массивом ObjectIds, а не только одним ObjectId?

Пример документа для заказа:

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

Не работает запрос:

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

Желаемый результат

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

Ответ 1

Этап конвейера агрегации $lookup не будет работать напрямую с массивом. Основной целью проекта является "левое соединение" как "один-много" типов соединения (или действительно "поиск" ) по возможным связанным данным. Но значение должно быть сингулярным, а не массивом.

Поэтому вы должны "де-нормализовать" содержимое сначала до выполнения операции $lookup, чтобы это работало. И это означает использование $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" }
    }}
])

После того, как $lookup соответствует каждому элементу массива, результат представляет собой массив, поэтому вы $unwind снова и $group до $push новые массивы для конечного результата.

Обратите внимание, что любое совпадение "левого соединения", которое не найдено, создаст пустой массив для "productObjects" на данном продукте и, таким образом, отрицает документ для элемента "продукт" при вызове второго $unwind.

Хотя непосредственное приложение для массива было бы неплохо, это просто то, как это работает в настоящее время, сопоставляя сингулярное значение с возможными многими.

Поскольку $lookup в основном очень новый, он в настоящее время работает так же, как и для тех, кто знаком с mongoose как "версия для бедных мужчин" предложенный там метод .populate(). Разница заключается в том, что $lookup предлагает "серверную" обработку "join" в отличие от клиента и что некоторая "зрелость" в $lookup в настоящее время отсутствует из того, что предлагает .populate() (например, интерполяция поиск непосредственно в массиве).

На самом деле это проблема, связанная с улучшением SERVER-22881, поэтому с некоторой удачей это ударит по следующей версии или вскоре.

Как принцип проектирования, ваша текущая структура не является ни хорошей, ни плохой, но просто подлежит накладным расходам при создании любого "соединения". Таким образом, применяется базовый постоянный принцип MongoDB в начале, где, если вы "можете" жить с данными "предварительно объединенными" в одной коллекции, тогда лучше всего это сделать.

Еще одна вещь, которую можно сказать о $lookup как общий принцип, заключается в том, что целью "присоединиться" здесь является работа по-другому, чем показано здесь. Поэтому вместо того, чтобы хранить "связанные идентификаторы" других документов в "родительском" документе, общий принцип, который лучше всего работает, заключается в том, где "связанные документы" содержат ссылку на "родительский".

Так что $lookup можно сказать, что "лучше работать" с "конструкцией отношений", которая противоположна тому, как что-то вроде mongoose .populate() выполняет на стороне клиента. Идентифицируя "один" внутри каждого "много" вместо этого, вы просто втягиваете связанные элементы, не требуя сначала $unwind массива.

Ответ 3

используйте $unwind, вы получите первый объект вместо массива объектов

запрос:

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

результат:

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

Ответ 4

Вы также можете использовать этап pipeline для выполнения проверок в массиве под-документов

Вот пример использования python (извините, я змеиный человек).

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

Подвох здесь заключается в том, чтобы сопоставить все объекты в array ObjectId (внешний _id который находится в local products field/prop).

Вы также можете очистить или проецировать зарубежные записи с дополнительной stage с, как показано комментарием выше.

Ответ 5

Агрегация с $lookup и последующим $group довольно громоздка, поэтому, если (и это среда, если) вы используете node и Mongoose или вспомогательную библиотеку с некоторыми подсказками в схеме, вы можете использовать .populate(), чтобы получить эти документы:

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