2016-01-23 83 views
37

对ObjectId数组的字段进行$ lookup查询的语法是什么,而不仅仅是一个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>} 
    ], 
} 
+0

是我与秩序的文件不够明确的例子吗?你想要的产品的示例文件? –

+0

SERVER-22881将按照预期的方式跟踪数组工作(而不是文字值)。 –

回答

63

$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可以说是“工作最好”与“关系设计”是相反的如何猫鼬.populate()执行它的客户端连接。通过将每个“多个”中的“一个”代入原来的概念,您就可以直接插入相关项目,而无需首先对阵列进行排序。

+0

谢谢你的作品!这是否表示我的数据未正确结构化/正常化? –

+1

@JasonLin不像“好/坏”那样直截了当,所以在答案中增加了更多的解释。这取决于什么适合你。 –

+2

目前的实施有些无意。查找本地字段数组中的所有值是有意义的,因此按字面顺序使用数组没有任何意义,所以SERVER-22881将跟踪修复。 –

-1

集结与$lookup和随后$group是相当繁琐的,所以如果(这就是如果一个中等)您使用节点&猫鼬或与架构,你可以一些提示的支持库使用.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") 
    ... 
1

使用$放松你会得到的,而不是对象

的数组的第一个对象

查询:

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