$ find查询数组中的ObjectId

时间:2016-01-23 18:58:13

标签: mongodb mongodb-query aggregation-framework

对于作为ObjectIds数组而不仅仅是单个ObjectId的字段执行$ lookup的语法是什么?

示例订单文档:

{
  _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>}
  ],
}

6 个答案:

答案 0 :(得分:111)

$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时取消“product”元素的文档

虽然对数组的直接应用会很好,但是通过将奇异值与可能的多个值匹配,这正是当前的工作原理。

由于$lookup基本上是非常新的,因此对于那些熟悉mongoose作为那里提供的.populate()方法的“穷人版本”的人来说,它当前是熟悉的。不同之处在于$lookup提供“服务器端”处理“加入”而不是客户端,$lookup中的某些“成熟度”目前缺少.populate()提供(例如直接在数组上插入查找)。

这实际上是一个指定的改进问题SERVER-22881,所以运气好的话会在下一个版本或之后很快发布。

作为一种设计原则,您当前的结构既不好也不坏,但在创建任何“连接”时只需要管理费用。因此,MongoDB在开始时的基本原则适用,如果你“可以”在一个集合中“预先加入”数据,那么最好这样做。

另一个可以说$lookup作为一般原则的事情是,这里“加入”的意图是反过来而不是这里所示。因此,不要将其他文档的“相关ID”保留在“父”文档中,最有效的一般原则是“相关文档”包含对“父”的引用。

所以$lookup可以说是“最好的”,其中“关系设计”与mongoose .populate()之类的东西相反,就像它的客户端连接一样。通过识别每个“很多”中的“一个”,您只需引入相关项目,而无需首先$unwind数组。

答案 1 :(得分:41)

$lookup聚合管道阶段NOW直接使用数组(on 3.3.4版本)。

请参阅:lookup between local (multiple)array of values and foreign (single) value

答案 2 :(得分:4)

使用 $ 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"
    }
}

答案 3 :(得分:3)

您还可以使用pipeline阶段对子文档数组执行检查

以下是使用python的例子(抱歉我是蛇人)。

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

此处的问题是匹配ObjectId array_id field / prop local中的products}中的所有对象。

您还可以使用其他stage来清理或投射外国记录,如上面的评论所示。

答案 4 :(得分:0)

selectors和后续$lookup进行汇总非常麻烦,所以如果(并且这是一个中等的话),您将使用节点&amp; Mongoose或支持库在模式中有一些提示,您可以使用the code here来获取这些文档:

$group

答案 5 :(得分:0)

我不得不不同意,如果我们在$ match阶段开始使用ID数组可以使$ lookup起作用。

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

如果要将查询结果传递到管道,它将变得更加复杂。但是,又有一种方法可以做到这一点(@ 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"
        }
    }
])