중첩 배열 내에서 일치하는 하위 문서 요소 만 반환
주요 컬렉션은 상점에 대한 배열을 포함하는 소매 업체입니다. 각 상점에는 일련의 오퍼가 포함되어 있습니다 (이 상점에서 구매할 수 있음). 이 제안 배열에는 크기 배열이 있습니다. (아래 예 참조)
이제 크기로 제공되는 모든 오퍼를 찾으려고합니다 L
.
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size": [
"XS",
"S",
"M"
]
},
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size": [
"S",
"L",
"XL"
]
}
]
}
}
이 쿼리를 시도했습니다. db.getCollection('retailers').find({'stores.offers.size': 'L'})
다음과 같은 출력을 기대합니다.
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size": [
"S",
"L",
"XL"
]
}
]
}
}
그러나 내 쿼리의 출력에는 size
XS, X 및 M 과 일치하지 않는 오퍼도 포함됩니다 .
MongoDB가 내 쿼리와 일치하는 오퍼 만 반환하도록 강제 할 수있는 방법은 무엇입니까?
인사와 감사합니다.
따라서 쿼리는 실제로 "문서"를 선택해야합니다. 그러나 찾고있는 것은 포함 된 "배열을 필터링"하여 반환 된 요소가 쿼리 조건과 만 일치하도록하는 것입니다.
실제 대답은 물론 그러한 세부 사항을 필터링하여 실제로 많은 대역폭을 절약하지 않는 한 시도조차하지 않거나 적어도 첫 번째 위치 일치를 넘어서는 것입니다.
MongoDB에는 쿼리 조건에서 일치하는 인덱스의 배열 요소를 반환하는 위치 $
연산자 가 있습니다. 그러나 이것은 "외부"대부분의 배열 요소의 "첫 번째"일치 인덱스 만 반환합니다.
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$': 1 }
)
이 경우 "stores"
배열 위치만을 의미합니다 . 따라서 여러 "stores"항목이있는 경우 일치하는 조건을 포함하는 요소 중 "하나"만 반환됩니다. 그러나 의 내부 배열에는 아무 작업도 수행하지 "offers"
않으므로 일치하는 "stores"
배열 내의 모든 "제공" 이 여전히 반환됩니다.
MongoDB는 표준 쿼리에서 이것을 "필터링"하는 방법이 없으므로 다음은 작동하지 않습니다.
db.getCollection('retailers').find(
{ 'stores.offers.size': 'L'},
{ 'stores.$.offers.$': 1 }
)
MongoDB가 실제로 이러한 수준의 조작을 수행하는 데 필요한 유일한 도구는 집계 프레임 워크를 사용하는 것입니다. 그러나 분석은 "아마도"이렇게하면 안되는 이유를 보여주고 대신 코드에서 배열을 필터링해야합니다.
버전별로이를 달성 할 수있는 순서대로.
먼저 MongoDB 3.2.x 에서 다음 $filter
작업 을 사용합니다 .
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", [] ]}
}
}
}}
])
그런 다음 MongoDB 2.6.x 이상에서 $map
및 $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]
]
}
}}
])
마지막으로 집계 프레임 워크가 도입 된 MongoDB 2.2.x 이상의 모든 버전에서 .
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"
}
}
}}
])
설명을 분해 해 보겠습니다.
MongoDB 3.2.x 이상
So generally speaking, $filter
is the way to go here since it is designed with the purpose in mind. Since there are multiple levels of the array, you need to apply this at each level. So first you are diving into each "offers"
within "stores"
to examime and $filter
that content.
The simple comparison here is "Does the "size"
array contain the element I am looking for". In this logical context, the short thing to do is use the $setIsSubset
operation to compare an array ("set") of ["L"]
to the target array. Where that condition is true
( it contains "L" ) then the array element for "offers"
is retained and returned in the result.
In the higher level $filter
, you are then looking to see if the result from that previous $filter
returned an empty array []
for "offers"
. If it is not empty, then the element is returned or otherwise it is removed.
MongoDB 2.6.x
This is very similar to the modern process except that since there is no $filter
in this version you can use $map
to inspect each element and then use $setDifference
to filter out any elements that were returned as false
.
So $map
is going to return the whole array, but the $cond
operation just decides whether to return the element or instead a false
value. In the comparison of $setDifference
to a single element "set" of [false]
all false
elements in the returned array would be removed.
In all other ways, the logic is the same as above.
MongoDB 2.2.x and up
So below MongoDB 2.6 the only tool for working with arrays is $unwind
, and for this purpose alone you should not use the aggregation framework "just" for this purpose.
The process indeed appears simple, by simply "taking apart" each array, filtering out the things you don't need then putting it back together. The main care is in the "two" $group
stages, with the "first" to re-build the inner array, and the next to re-build the outer array. There are distinct _id
values at all levels, so these just need to be included at every level of grouping.
But the problem is that $unwind
is very costly. Though it does have purpose still, it's main usage intent is not to do this sort of filtering per document. In fact in modern releases it's only usage should be when an element of the array(s) needs to become part of the "grouping key" itself.
Conclusion
So it's not a simple process to get matches at multiple levels of an array like this, and in fact it can be extremely costly if implemented incorrectly.
Only the two modern listings should ever be used for this purpose, as they employ a "single" pipeline stage in addition to the "query" $match
in order to do the "filtering". The resulting effect is little more overhead than the standard forms of .find()
.
In general though, those listings still have an amount of complexity to them, and indeed unless you are really drastically reducing the content returned by such filtering in a way that makes a significant improvement in bandwidth used between the server and client, then you are better of filtering the result of the initial query and basic projection.
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);
})
So working with the returned object "post" query processing is far less obtuse than using the aggregation pipeline to do this. And as stated the only "real" diffrerence would be that you are discarding the other elements on the "server" as opposed to removing them "per document" when received, which may save a little bandwidth.
But unless you are doing this in a modern release with only $match
and $project
, then the "cost" of processing on the server will greatly outweigh the "gain" of reducing that network overhead by stripping the unmatched elements first.
In all cases, you get the same result:
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"stores" : [
{
"_id" : ObjectId("56f277b5279871c20b8b4783"),
"offers" : [
{
"_id" : ObjectId("56f277b1279871c20b8b4567"),
"size" : [
"S",
"L",
"XL"
]
}
]
}
]
}
as your array is embeded we cannot use $elemMatch, instead you can use aggregation framework to get your results:
db.retailers.aggregate([
{$match:{"stores.offers.size": 'L'}}, //just precondition can be skipped
{$unwind:"$stores"},
{$unwind:"$stores.offers"},
{$match:{"stores.offers.size": 'L'}},
{$group:{
_id:{id:"$_id", "storesId":"$stores._id"},
"offers":{$push:"$stores.offers"}
}},
{$group:{
_id:"$_id.id",
stores:{$push:{_id:"$_id.storesId","offers":"$offers"}}
}}
]).pretty()
what this query does is unwinds arrays (twice), then matches size and then reshapes the document to previous form. You can remove $group steps and see how it prints. Have a fun!
ReferenceURL : https://stackoverflow.com/questions/36229123/return-only-matched-sub-document-elements-within-a-nested-array
'IT TIP' 카테고리의 다른 글
Golang의 Bcrypt 비밀번호 해싱 (Node.js와 호환)? (0) | 2020.12.29 |
---|---|
WKWebView에서 모든 쿠키 가져 오기 (0) | 2020.12.29 |
바이트는 다르지만 값은 같은 유니 코드 문자열을 어떻게 비교합니까? (0) | 2020.12.29 |
viewstate를 디코딩하는 방법 (0) | 2020.12.29 |
UITableView에 바닥 글을 추가하는 방법은 무엇입니까? (0) | 2020.12.29 |