$在数组中查找 ObjectId

对 ObjectId 数组而不仅仅是单个 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>}
],
}
177351 次浏览

二零一七年最新情况

$lookup 现在可以直接使用数组作为本地字段

旧答案

$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的新数组得到最终结果。

注意,任何未找到的“ left join”匹配将为给定产品上的“ productObjects”创建一个空数组,从而在调用第二个 $unwind时为“ product”元素取消文档。

虽然直接应用于数组会很好,但是这只是当前通过匹配一个单数值到一个可能的多数值来实现的。

由于 $lookup基本上是非常新的,它目前的工作方式与那些熟悉 猫鼬的人所熟悉的 .populate()方法的“穷人版本”一样。不同之处在于,与客户端相比,$lookup提供了“连接”的“服务器端”处理,而且 $lookup中的一些“成熟度”目前还不如 .populate()(比如直接在数组上进行内插查找)。

这实际上是一个改进 服务器编号22881的指定问题,所以如果运气好的话,这个问题可能会在下一个版本中出现,或者在不久之后出现。

作为一个设计原则,您当前的结构既不好也不坏,只是在创建任何“连接”时受制于开销。因此,MongoDB 在初始阶段的基本原则是适用的,如果您“可以”使用一个集合中的“预加入”数据,那么最好这样做。

另一件可以说是 $lookup作为一个通用原则的事情是,这里的“连接”的意图是以另一种方式工作,而不是这里显示的方式。因此,与其将其他文档的“相关 id”保留在“父”文档中,最有效的一般原则是“相关文档”包含对“父”的引用。

因此,$lookup可以说是“工作最好”的“关系设计”,这是相反的东西,如猫鼬 .populate()执行其客户端连接。通过识别每个“多”中的“一”来代替,然后您只需拉入相关的项目,而不需要首先 $unwind数组。

从 MongoDB v3.4(2016年发布)开始,就不再需要 $unwind了。

这是在 服务器编号22881中追踪到的。

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

使用 $放松,您将得到第一个对象,而不是对象数组

查询:

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

还可以使用 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(local字段/支撑 products中的外部 _id)中的所有对象。

您还可以使用额外的 stage清理或投影外部记录,如上面的注释所示。

我不同意,如果我们使用 $match stage 作为前缀,我们可以使用 ID 数组进行 $查找。

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

查找基本上是关系数据库的左连接操作。 所以左边的文档(关系数据库术语中的表)是我们当前工作的文档,右边的文档是我们必须从中提取信息的文档。

查阅->

   {
$lookup:
{
from: <right document>,
localField: <  field in left document which holds the info for right document >,
foreignField: <field in the right document which is referenced in left document  >,
as: <alias for array list where the result is to be stored.>
}
}

所以上述问题的正确答案是->

{
"$lookup":
{
"from": "products",
"localField": "products",
"foreignField": "_id",
"as": "productObject",
}
}

但是它对我不起作用,我调试并发现了下面提到的问题: 所以,在我的例子中,在左边的文档中插入时我做错了:

错误的插入->

产品文件->

{
"_id": ObjectId(something),
"products":[ "some_id_1", "some_id_2"]
}

右边的插入应该是->

产品文件

 {
"_id": ObjectId(something),
"products" : [ ObjectId("some_id_1"), ObjectId("some_id_2")]
}

对我来说,我将对象 id 存储为字符串,而不是像 ObjectId 对象那样存储在左侧文档的外键中。 快乐确保正确的格式,同时作出插入。

最后,我们都应该从某人的错误中吸取教训。