2016-03-15 22 views
10

Tôi cần truy xuất toàn bộ phân cấp đối tượng đơn lẻ từ cơ sở dữ liệu dưới dạng JSON. Trên thực tế, đề xuất về bất kỳ giải pháp nào khác để đạt được kết quả này sẽ được đánh giá cao. Tôi quyết định sử dụng MongoDB với hỗ trợ tra cứu $.MongoDB tra cứu lồng nhau với 3 cấp độ

Vì vậy, tôi có ba bộ sưu tập:

bên

{ "_id" : "2", "name" : "party2" } 
{ "_id" : "5", "name" : "party5" } 
{ "_id" : "4", "name" : "party4" } 
{ "_id" : "1", "name" : "party1" } 
{ "_id" : "3", "name" : "party3" }  

địa chỉ

{ "_id" : "a3", "street" : "Address3", "party_id" : "2" } 
{ "_id" : "a6", "street" : "Address6", "party_id" : "5" } 
{ "_id" : "a1", "street" : "Address1", "party_id" : "1" } 
{ "_id" : "a5", "street" : "Address5", "party_id" : "5" } 
{ "_id" : "a2", "street" : "Address2", "party_id" : "1" } 
{ "_id" : "a4", "street" : "Address4", "party_id" : "3" } 

addressComment

{ "_id" : "ac2", "address_id" : "a1", "comment" : "Comment2" } 
{ "_id" : "ac1", "address_id" : "a1", "comment" : "Comment1" } 
{ "_id" : "ac5", "address_id" : "a5", "comment" : "Comment6" } 
{ "_id" : "ac4", "address_id" : "a3", "comment" : "Comment4" } 
{ "_id" : "ac3", "address_id" : "a2", "comment" : "Comment3" } 

Tôi cần truy xuất tất cả các bên có tất cả các địa chỉ và nhận xét địa chỉ tương ứng như là một phần của hồ sơ. Tập hợp của tôi:

db.party.aggregate([{ 
    $lookup: { 
     from: "address", 
     localField: "_id", 
     foreignField: "party_id", 
     as: "address" 
    } 
}, 
{ 
    $unwind: "$address" 
}, 
{ 
    $lookup: { 
     from: "addressComment", 
     localField: "address._id", 
     foreignField: "address_id", 
     as: "address.addressComment" 
    } 
}]) 

Kết quả là khá lạ. Một số bản ghi là ok. Nhưng Đảng với _id 4 bị thiếu (không có địa chỉ cho nó). Ngoài ra còn có hai bên _id 1 trong tập kết quả (nhưng có địa chỉ khác nhau):

{ 
    "_id": "1", 
    "name": "party1", 
    "address": { 
     "_id": "2", 
     "street": "Address2", 
     "party_id": "1", 
     "addressComment": [{ 
      "_id": "3", 
      "address_id": "2", 
      "comment": "Comment3" 
     }] 
    } 
}{ 
    "_id": "1", 
    "name": "party1", 
    "address": { 
     "_id": "1", 
     "street": "Address1", 
     "party_id": "1", 
     "addressComment": [{ 
      "_id": "1", 
      "address_id": "1", 
      "comment": "Comment1" 
     }, 
     { 
      "_id": "2", 
      "address_id": "1", 
      "comment": "Comment2" 
     }] 
    } 
}{ 
    "_id": "3", 
    "name": "party3", 
    "address": { 
     "_id": "4", 
     "street": "Address4", 
     "party_id": "3", 
     "addressComment": [] 
    } 
}{ 
    "_id": "5", 
    "name": "party5", 
    "address": { 
     "_id": "5", 
     "street": "Address5", 
     "party_id": "5", 
     "addressComment": [{ 
      "_id": "5", 
      "address_id": "5", 
      "comment": "Comment5" 
     }] 
    } 
}{ 
    "_id": "2", 
    "name": "party2", 
    "address": { 
     "_id": "3", 
     "street": "Address3", 
     "party_id": "2", 
     "addressComment": [{ 
      "_id": "4", 
      "address_id": "3", 
      "comment": "Comment4" 
     }] 
    } 
} 

Hãy giúp tôi với điều này. Tôi khá mới với MongoDB nhưng tôi cảm thấy nó có thể làm những gì tôi cần từ nó.

Trả lời

19

Nguyên nhân gây ra 'rắc rối' của bạn là giai đoạn tổng hợp thứ hai - { $unwind: "$address" }. Nó loại bỏ bản ghi cho bên với _id: 4 (vì mảng địa chỉ của nó trống, như bạn đề cập) và tạo hai bản ghi cho các bên _id: 1_id: 5 (vì mỗi người trong số họ có hai địa chỉ).

  • Để ngăn chặn loại bỏ của các bên không có địa chỉ bạn nên đặt preserveNullAndEmptyArrays tùy chọn của giai đoạn $unwind để true.

  • Để ngăn trùng lặp các bên cho các địa chỉ khác nhau của nó, bạn nên thêm $group giai đoạn tổng hợp vào đường ống của mình. Ngoài ra, sử dụng $project giai đoạn với nhà điều hành $filter để loại trừ bản ghi địa chỉ trống trong đầu ra.

db.party.aggregate([{ 
    $lookup: { 
    from: "address", 
    localField: "_id", 
    foreignField: "party_id", 
    as: "address" 
    } 
}, { 
    $unwind: { 
    path: "$address", 
    preserveNullAndEmptyArrays: true 
    } 
}, { 
    $lookup: { 
    from: "addressComment", 
    localField: "address._id", 
    foreignField: "address_id", 
    as: "address.addressComment", 
    } 
}, { 
    $group: { 
    _id : "$_id", 
    name: { $first: "$name" }, 
    address: { $push: "$address" } 
    } 
}, { 
    $project: { 
    _id: 1, 
    name: 1, 
    address: { 
     $filter: { input: "$address", as: "a", cond: { $ifNull: ["$$a._id", false] } } 
    } 
    } 
}]); 
+0

Tank you Shad! Có một vấn đề nhỏ với kỷ lục 4 mặc dù: '{ \t "_id": "4", \t "tên": "party4", \t "địa chỉ": [{ \t \t "addressComment": [] \t}] } ' Như bạn có thể thấy - địa chỉ phải là rỗng nhưng thay vào đó là bản ghi trống ... Chúng tôi có thể bỏ qua địa chỉ này nếu địa chỉ của nó trống không? Trong trường hợp khác, địa chỉ này sẽ được coi là một bản ghi. – Yuriy

+1

Thực ra tôi thấy rằng giải pháp được cung cấp hoạt động như mong đợi theo mô tả của trường "preserveNullAndEmptyArrays" mới cho hoạt động $ relax (kể từ 3.2). Bây giờ chúng ta có thể bỏ qua bước "$ project" và chỉ định "$ relax" thay vì đơn giản: '$ unwind: {path:" $ address ", preserveNullAndEmptyArrays: true}'.Tôi sẽ chấp nhận câu trả lời của bạn, cảm ơn bạn đã trả lời nhanh chóng và rõ ràng! – Yuriy

+0

@Shad Tôi có câu hỏi khá giống nhau. Ở đây mã OP chỉ có một thuộc tính được gọi là 'name' trong bộ sưu tập' party' và do đó bạn đã sử dụng '$ first' để lấy nó trong' $ group'. Giả sử tôi có 10 thuộc tính, sau đó có cách nào để tự động nhận tất cả thuộc tính mà không đề cập đến từng thuộc tính riêng lẻ không? – Xyroid

Các vấn đề liên quan