2016-01-23 88 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" 
    } 
}