Vì vậy, truy vấn bạn đã thực sự chọn "tài liệu" giống như nó phải. Nhưng những gì bạn đang tìm là "lọc các mảng" được chứa để các phần tử được trả về chỉ phù hợp với điều kiện của truy vấn.
Tất nhiên, câu trả lời thực sự là trừ khi bạn thực sự tiết kiệm được rất nhiều băng thông bằng cách lọc ra những chi tiết như vậy, thì bạn thậm chí không nên thử, hoặc ít nhất là ngoài kết quả phù hợp vị trí đầu tiên.
MongoDB có một $
vị trí toán tử sẽ trả về một phần tử mảng tại chỉ mục phù hợp từ một điều kiện truy vấn. Tuy nhiên, điều này chỉ trả về chỉ mục phù hợp "đầu tiên" của phần tử mảng "bên ngoài" nhất.
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
)
Trong trường hợp này, nó có nghĩa là "stores"
chỉ vị trí mảng. Vì vậy, nếu có nhiều mục nhập "cửa hàng", thì chỉ "một" trong số các phần tử có chứa điều kiện phù hợp của bạn sẽ được trả về. Nhưng , điều đó không có tác dụng gì đối với mảng bên trong "offers"
và như vậy mọi "phiếu mua hàng" trong "stores"
đối sánh mảng sẽ vẫn được trả về.
MongoDB không có cách nào để "lọc" điều này trong một truy vấn tiêu chuẩn, vì vậy điều sau không hoạt động:
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$.offers.$': 1 }
)
Công cụ duy nhất mà MongoDB thực sự phải thực hiện mức thao tác này là với khung tổng hợp. Nhưng phân tích sẽ cho bạn thấy lý do tại sao bạn "có thể" không nên làm điều này và thay vào đó chỉ lọc mảng trong mã.
Để làm cách nào bạn có thể đạt được điều này trên mỗi phiên bản.
Đầu tiên với MongoDB 3.2.x với việc sử dụng $filter
hoạt động:
db.getCollection('retailers').aggregate([
{ "$match": { "stores.offers.size": "L" } },
{ "$project": {
"stores": {
"$filter": {
"input": {
"$map": {
"input": "$stores",
"as": "store",
"in": {
"_id": "$$store._id",
"offers": {
"$filter": {
"input": "$$store.offers",
"as": "offer",
"cond": {
"$setIsSubset": [ ["L"], "$$offer.size" ]
}
}
}
}
}
},
"as": "store",
"cond": { "$ne": [ "$$store.offers", [] ]}
}
}
}}
])
Sau đó với MongoDB 2.6.x trở lên với $map
và $setDifference
:
db.getCollection('retailers').aggregate([
{ "$match": { "stores.offers.size": "L" } },
{ "$project": {
"stores": {
"$setDifference": [
{ "$map": {
"input": {
"$map": {
"input": "$stores",
"as": "store",
"in": {
"_id": "$$store._id",
"offers": {
"$setDifference": [
{ "$map": {
"input": "$$store.offers",
"as": "offer",
"in": {
"$cond": {
"if": { "$setIsSubset": [ ["L"], "$$offer.size" ] },
"then": "$$offer",
"else": false
}
}
}},
[false]
]
}
}
}
},
"as": "store",
"in": {
"$cond": {
"if": { "$ne": [ "$$store.offers", [] ] },
"then": "$$store",
"else": false
}
}
}},
[false]
]
}
}}
])
Và cuối cùng trong bất kỳ phiên bản nào ở trên MongoDB 2.2.x nơi mà khung tổng hợp đã được giới thiệu.
db.getCollection('retailers').aggregate([
{ "$match": { "stores.offers.size": "L" } },
{ "$unwind": "$stores" },
{ "$unwind": "$stores.offers" },
{ "$match": { "stores.offers.size": "L" } },
{ "$group": {
"_id": {
"_id": "$_id",
"storeId": "$stores._id",
},
"offers": { "$push": "$stores.offers" }
}},
{ "$group": {
"_id": "$_id._id",
"stores": {
"$push": {
"_id": "$_id.storeId",
"offers": "$offers"
}
}
}}
])
Hãy chia nhỏ các giải thích.
MongoDB 3.2.x trở lên
Vì vậy, nói chung, $filter
là cách để đi đến đây vì nó được thiết kế với mục đích trong tâm trí. Vì có nhiều cấp độ của mảng, bạn cần áp dụng điều này ở mỗi cấp độ. Vì vậy, trước tiên bạn đi sâu vào từng "offers"
trong "stores"
đến thời gian kiểm tra và $filter
nội dung đó.
So sánh đơn giản ở đây là "Kích thước "size"
mảng chứa phần tử tôi đang tìm kiếm " . Trong ngữ cảnh hợp lý này, điều ngắn gọn cần làm là sử dụng $setIsSubset
hoạt động để so sánh một mảng ("set") của ["L"]
vào mảng đích. Trường hợp điều kiện đó là true
(nó chứa "L") sau đó là phần tử mảng cho "offers"
được giữ lại và trả về trong kết quả.
Ở cấp cao hơn $filter
, sau đó bạn đang tìm kiếm xem kết quả từ $filter
trước đó trả về một mảng trống []
cho "offers"
. Nếu nó không trống, thì phần tử sẽ được trả về hoặc nếu không thì nó sẽ bị xóa.
MongoDB 2.6.x
Điều này rất giống với quy trình hiện đại ngoại trừ việc không có $filter
trong phiên bản này, bạn có thể sử dụng $map
để kiểm tra từng phần tử và sau đó sử dụng $setDifference
để lọc ra bất kỳ phần tử nào được trả về là false
.
Vì vậy, $map
sẽ trả về toàn bộ mảng, nhưng $cond
hoạt động chỉ quyết định trả về phần tử hay thay vào đó là false
giá trị. So sánh với $setDifference
thành một phần tử duy nhất "tập hợp" [false]
tất cả false
các phần tử trong mảng được trả về sẽ bị xóa.
Theo tất cả các cách khác, logic tương tự như trên.
MongoDB 2.2.x trở lên
Vì vậy, bên dưới MongoDB 2.6, công cụ duy nhất để làm việc với mảng là $unwind
và chỉ vì mục đích này, bạn không nên sử dụng khung tổng hợp "chỉ" cho mục đích này.
Quá trình này thực sự có vẻ đơn giản, chỉ bằng cách "tách rời" từng mảng, lọc ra những thứ bạn không cần sau đó ghép chúng lại với nhau. Việc chăm sóc chính nằm trong nhóm $
"hai" các giai đoạn, với "đầu tiên" để xây dựng lại mảng bên trong và bước tiếp theo để xây dựng lại mảng bên ngoài. Có _id
riêng biệt các giá trị ở tất cả các cấp, vì vậy những giá trị này chỉ cần được đưa vào ở mọi cấp nhóm.
Nhưng vấn đề là $unwind
rất tốn kém . Mặc dù nó vẫn có mục đích, nhưng mục đích sử dụng chính của nó không phải để thực hiện loại lọc này trên mỗi tài liệu. Trên thực tế, trong các bản phát hành hiện đại, nó chỉ được sử dụng khi một phần tử của (các) mảng cần trở thành một phần của chính "khóa nhóm".
Kết luận
Vì vậy, đó không phải là một quá trình đơn giản để có được các kết quả phù hợp ở nhiều cấp của một mảng như thế này và trên thực tế, nó có thể cực kỳ tốn kém nếu được triển khai không chính xác.
Chỉ hai danh sách hiện đại mới được sử dụng cho mục đích này, vì chúng sử dụng một giai đoạn đường dẫn "duy nhất" ngoài "truy vấn" $match
để thực hiện "lọc". Hiệu quả tạo ra có chi phí cao hơn một chút so với các dạng chuẩn của .find()
.
Mặc dù vậy, nói chung, những danh sách đó vẫn có một số phức tạp đối với chúng và thực sự trừ khi bạn thực sự giảm đáng kể nội dung được trả về bởi bộ lọc như vậy theo cách tạo ra sự cải thiện đáng kể về băng thông được sử dụng giữa máy chủ và máy khách, thì bạn sẽ tốt hơn lọc kết quả của truy vấn ban đầu và phép chiếu cơ bản.
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
).forEach(function(doc) {
// Technically this is only "one" store. So omit the projection
// if you wanted more than "one" match
doc.stores = doc.stores.filter(function(store) {
store.offers = store.offers.filter(function(offer) {
return offer.size.indexOf("L") != -1;
});
return store.offers.length != 0;
});
printjson(doc);
})
Vì vậy, làm việc với đối tượng trả về xử lý truy vấn "post" ít khó khăn hơn so với việc sử dụng đường ống tổng hợp để thực hiện điều này. Và như đã nói, điểm khác biệt "thực sự" duy nhất là bạn đang loại bỏ các phần tử khác trên "máy chủ" thay vì xóa chúng "trên mỗi tài liệu" khi nhận được, điều này có thể tiết kiệm một chút băng thông.
Nhưng trừ khi bạn đang làm điều này trong một bản phát hành hiện đại với chỉ $match
và $project
, thì "chi phí" của quá trình xử lý trên máy chủ sẽ lớn hơn rất nhiều "lợi ích" của việc giảm chi phí mạng đó bằng cách loại bỏ các phần tử chưa từng có trước.
Trong mọi trường hợp, bạn đều nhận được cùng một kết quả:
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size" : [
"S",
"L",
"XL"
]
}
]
}
]
}