MongoDB 컬렉션의 객체 배열에서 쿼리 된 요소 만 검색


377

내 컬렉션에 다음과 같은 문서가 있다고 가정합니다.

{  
   "_id":ObjectId("562e7c594c12942f08fe4192"),
   "shapes":[  
      {  
         "shape":"square",
         "color":"blue"
      },
      {  
         "shape":"circle",
         "color":"red"
      }
   ]
},
{  
   "_id":ObjectId("562e7c594c12942f08fe4193"),
   "shapes":[  
      {  
         "shape":"square",
         "color":"black"
      },
      {  
         "shape":"circle",
         "color":"green"
      }
   ]
}

쿼리를 수행하십시오.

db.test.find({"shapes.color": "red"}, {"shapes.color": 1})

또는

db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1})

일치하는 문서 (문서 1)를 반환 하지만 항상 모든 배열 항목을 포함합니다 shapes.

{ "shapes": 
  [
    {"shape": "square", "color": "blue"},
    {"shape": "circle", "color": "red"}
  ] 
}

그러나 다음 을 포함하는 배열로만 문서 (문서 1) 를 얻고 싶습니다 color=red.

{ "shapes": 
  [
    {"shape": "circle", "color": "red"}
  ] 
}

어떻게해야합니까?

답변:


416

MongoDB 2.2의 새로운 $elemMatch프로젝션 연산자는 첫 번째로 일치하는 shapes요소 만 포함하도록 반환 된 문서를 변경하는 다른 방법을 제공합니다 .

db.test.find(
    {"shapes.color": "red"}, 
    {_id: 0, shapes: {$elemMatch: {color: "red"}}});

보고:

{"shapes" : [{"shape": "circle", "color": "red"}]}

2.2에서는을 사용하여이 작업을 수행 할 수도 있습니다 $ projection operator. 여기서 $투영 객체의 필드 이름은 쿼리에서 필드의 첫 번째 일치하는 배열 요소의 인덱스를 나타냅니다. 다음은 위와 동일한 결과를 반환합니다.

db.test.find({"shapes.color": "red"}, {_id: 0, 'shapes.$': 1});

MongoDB 3.2 업데이트

3.2 릴리스부터는 새로운 $filter집계 연산자를 사용하여 프로젝션 중에 배열을 필터링 할 수 있습니다 . 첫 번째 배열 대신 모든 일치 항목 을 포함시킬 수 있다는 이점이 있습니다.

db.test.aggregate([
    // Get just the docs that contain a shapes element where color is 'red'
    {$match: {'shapes.color': 'red'}},
    {$project: {
        shapes: {$filter: {
            input: '$shapes',
            as: 'shape',
            cond: {$eq: ['$$shape.color', 'red']}
        }},
        _id: 0
    }}
])

결과 :

[ 
    {
        "shapes" : [ 
            {
                "shape" : "circle",
                "color" : "red"
            }
        ]
    }
]

15
첫 번째가 아닌 일치하는 모든 요소를 ​​반환하려는 경우 어떤 해결책이 있습니까?
Steve Ng

나는 내가 사용하고 두려워 몽고 3.0.X를 :-(
charliebrownie

@charliebrownie 그런 다음 사용하는 다른 답변 중 하나를 사용하십시오 aggregate.
JohnnyHK

이 쿼리는 배열 "모양"만 반환하며 다른 필드는 반환하지 않습니다. 다른 필드를 반환하는 방법을 아는 사람이 있습니까?
Mark Thien

1
db.test.find({}, {shapes: {$elemMatch: {color: "red"}}});
Paul

97

MongoDB 2.2+ 의 새로운 Aggregation Framework 는 Map / Reduce의 대안을 제공합니다. $unwind연산자 별도하는 데 사용할 수있는 shapes정합 될 수있는 문서의 스트림으로 배열 :

db.test.aggregate(
  // Start with a $match pipeline which can take advantage of an index and limit documents processed
  { $match : {
     "shapes.color": "red"
  }},
  { $unwind : "$shapes" },
  { $match : {
     "shapes.color": "red"
  }}
)

결과 :

{
    "result" : [
        {
            "_id" : ObjectId("504425059b7c9fa7ec92beec"),
            "shapes" : {
                "shape" : "circle",
                "color" : "red"
            }
        }
    ],
    "ok" : 1
}

6
@JohnnyHK :이 경우 $elemMatch또 다른 옵션입니다. 실제로 $ elemMatch가 작동하지 않는 Google 그룹 질문 을 통해 여기에 도달했습니다. 문서 당 첫 번째 일치 항목 만 반환하기 때문입니다.
Stennie

1
고마워, 나는 그 한계를 알지 못했기 때문에 알기에 좋다. 응답 한 내 댓글을 삭제하여 죄송합니다. 다른 답변을 게시하기로 결정했으며 사람들을 혼동하고 싶지 않았습니다.
JohnnyHK

3
@JohnnyHK : 걱정할 필요가 없습니다. 이제 질문에 대한 세 가지 유용한 답변이 있습니다. ;-)
Stennie

다른 검색 자에게는 이것 외에도 추가를 시도했지만 { $project : { shapes : 1 } }작동하는 것처럼 보였고 동봉하는 문서가 커서 shapes키 값 을 보려고 할 때 도움이되었습니다 .
user1063287

2
@ calmbird 초기 $ match 단계를 포함하도록 예제를 업데이트했습니다. 보다 효율적인 기능 제안에 관심이 있다면 SERVER-6612 : MongoDB 이슈 트래커 의 $ elemMatch 투영 지정자와 같은 투영에서 다중 배열 값 투영을 지원합니다 .
Stennie

30

또 다른 중요한 방법은 MongoDB 2.6 의 새로운 집계 기능 중 하나 인 $ redact 를 사용 하는 것입니다 . 2.6을 사용하는 경우 큰 배열이있는 경우 성능 문제를 일으킬 수있는 $ unwind가 필요하지 않습니다.

db.test.aggregate([
    { $match: { 
         shapes: { $elemMatch: {color: "red"} } 
    }},
    { $redact : {
         $cond: {
             if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]},
             then: "$$DESCEND",
             else: "$$PRUNE"
         }
    }}]);

$redact "문서 자체에 저장된 정보를 기반으로 문서의 내용을 제한합니다" . 따라서 문서 내부 에서만 실행됩니다 . 기본적으로 문서를 맨 아래까지 스캔하고에있는 if조건 과 일치하는지 확인합니다. 일치하는 $cond경우 content ( $$DESCEND) 또는 remove ( $$PRUNE)를 유지합니다 .

위의 예에서 먼저 $match전체 shapes배열을 반환 하고 $ redact는 배열을 예상 결과로 제거합니다.

참고 {$not:"$color"}그것뿐만 아니라 상단의 문서를 스캔하기 때문에, 필요하며, 경우 $redact찾을 수없는 color최고 수준에 필드를이 반환됩니다 false그것이 우리가 원하지 않는 문서 전체를 제거 할 수도 있습니다.


1
완벽한 답변. 언급했듯이 $ unwind는 많은 RAM을 소비합니다. 따라서 비교할 때 더 좋습니다.
manojpt

의심 스럽다. 이 예에서 "shapes"는 배열입니다. "$ redact"는 "shapes"배열의 모든 개체를 검사합니까? 이것이 성능과 관련하여 어떻게 좋을까요?
manojpt

모든 것이 아니라 첫 경기 결과입니다. 그것이 $match당신이 첫 집계 단계로
꼽는

okkk .. "color"필드에 인덱스가 생성 된 경우에도 "shape"배열의 모든 개체를 스캔합니까 ??? 배열에서 여러 객체를 일치시키는 효율적인 방법은 무엇입니까?
manojpt

2
훌륭한! $ eq가 어떻게 작동하는지 이해하지 못합니다. 나는 그것을 원래 그대로 두었고 이것은 나를 위해 작동하지 않았다. 어쨌든, 그것은 일치하는 것을 찾기 위해 도형의 배열을 보지만, 쿼리는 어떤 배열을 찾을 것인지 지정하지 않습니다. 문서에 모양과 크기가 있다면; $ eq는 두 배열 모두에서 일치하는 것을 볼 수 있습니까? $ redact가 문서 내에서 'if'조건과 일치하는 것을 찾고 있습니까?
Onosa

30

주의 : 이 답변은 MongoDB 2.2 이상의 새로운 기능이 도입되기 전에 당시 관련 솔루션을 제공합니다 . 최신 버전의 MongoDB를 사용하는 경우 다른 답변을 참조하십시오.

필드 선택기 매개 변수는 완전한 특성으로 제한됩니다. 배열의 일부를 선택하는 데 사용할 수 없으며 전체 배열 만 선택할 수 있습니다. $ positional operator 사용하려고했지만 작동하지 않았습니다.

가장 쉬운 방법은 클라이언트 에서 도형 필터링하는 것 입니다.

실제로 MongoDB에서 직접 올바른 출력 이 필요한 경우 map-reduce사용 하여 모양을 필터링 할 수 있습니다 .

function map() {
  filteredShapes = [];

  this.shapes.forEach(function (s) {
    if (s.color === "red") {
      filteredShapes.push(s);
    }
  });

  emit(this._id, { shapes: filteredShapes });
}

function reduce(key, values) {
  return values[0];
}

res = db.test.mapReduce(map, reduce, { query: { "shapes.color": "red" } })

db[res.result].find()

24

배열 $slice에서 중요한 객체를 반환하는 데 도움이되므로 배열 요소를 일치시키는 쿼리를 더 잘 수행 할 수 있습니다 .

db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})

$slice요소의 색인을 알고 있지만 때로는 어떤 기준 요소와 일치하는 배열 요소를 원할 때 유용합니다. $연산자 와 일치하는 요소를 반환 할 수 있습니다 .


19
 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

출력

{

   "shapes" : [ 
       {
           "shape" : "circle",
           "color" : "red"
       }
   ]
}

12

mongodb에서 find의 구문은 다음과 같습니다.

    db.<collection name>.find(query, projection);

그리고 두 번째로 작성한 쿼리는

    db.test.find(
    {shapes: {"$elemMatch": {color: "red"}}}, 
    {"shapes.color":1})

여기에서는 $elemMatch쿼리 부분에서 연산자를 사용했지만 투영 부분에서이 연산자를 사용하면 원하는 결과를 얻을 수 있습니다. 검색어를 다음과 같이 작성할 수 있습니다

     db.users.find(
     {"shapes.color":"red"},
     {_id:0, shapes: {$elemMatch : {color: "red"}}})

원하는 결과를 얻을 수 있습니다.


1
이것은 나를 위해 작동합니다. 그러나 "shapes.color":"red"쿼리 매개 변수 (찾기 메서드의 첫 번째 매개 변수)에는 필요하지 않은 것 같습니다. 당신은 그것을 대체 {}하고 동일한 결과를 얻을 수 있습니다 .
Erik Olson

2
@ErikOlson 귀하의 제안은 위의 경우에 옳습니다. 여기서 붉은 색으로 된 모든 문서를 찾아서에만 투영을 적용해야합니다. 그러나 누군가가 파란색으로 된 모든 문서를 찾아야하지만 색상이 빨간색 인 도형 배열의 요소 만 반환해야한다고 가정 해 봅시다. 이 경우 위의 쿼리는 다른 사람도 참조 할 수 있습니다.
Vicky

이것은 가장 쉬운 것처럼 보이지만 그것을 작동시킬 수는 없습니다. 첫 번째로 일치하는 하위 문서 만 반환합니다.
newman

8

JohnnyHK 에게 감사합니다 .

여기에 좀 더 복잡한 사용법을 추가하고 싶습니다.

// Document 
{ 
"_id" : 1
"shapes" : [
  {"shape" : "square",  "color" : "red"},
  {"shape" : "circle",  "color" : "green"}
  ] 
} 

{ 
"_id" : 2
"shapes" : [
  {"shape" : "square",  "color" : "red"},
  {"shape" : "circle",  "color" : "green"}
  ] 
} 


// The Query   
db.contents.find({
    "_id" : ObjectId(1),
    "shapes.color":"red"
},{
    "_id": 0,
    "shapes" :{
       "$elemMatch":{
           "color" : "red"
       } 
    }
}) 


//And the Result

{"shapes":[
    {
       "shape" : "square",
       "color" : "red"
    }
]}

7

당신은 단지 쿼리를 실행해야합니다

db.test.find(
{"shapes.color": "red"}, 
{shapes: {$elemMatch: {color: "red"}}});

이 쿼리의 출력은

{
    "_id" : ObjectId("562e7c594c12942f08fe4192"),
    "shapes" : [ 
        {"shape" : "circle", "color" : "red"}
    ]
}

예상대로 배열에서 색상 : '빨간색'과 일치하는 정확한 필드를 제공합니다.


3

$ project와 함께 다른 현명한 일치 요소가 문서의 다른 요소와 함께 클럽 화되는 것이 더 적합합니다.

db.test.aggregate(
  { "$unwind" : "$shapes" },
  { "$match" : {
     "shapes.color": "red"
  }},
{"$project":{
"_id":1,
"item":1
}}
)

이것이 입력 및 출력 세트로 달성한다고 설명 할 수 있습니까?
Alexander Mills

2

마찬가지로 당신은 여러에 대한 찾을 수 있습니다

db.getCollection('localData').aggregate([
    // Get just the docs that contain a shapes element where color is 'red'
  {$match: {'shapes.color': {$in : ['red','yellow'] } }},
  {$project: {
     shapes: {$filter: {
        input: '$shapes',
        as: 'shape',
        cond: {$in: ['$$shape.color', ['red', 'yellow']]}
     }}
  }}
])

이 대답은 참으로 선호하는 4.x의 방법이다 : $match다음, 공간을 줄일 수 $filter의 (입력 필드를 덮어, 당신이 원하는 것을 사용 출력을 유지하기 위해 $filter필드 shapes$project에에 다시 shapes. 스타일 참고 : 최고로 필드 이름을 사용하지 as그 이후로 혼란을 인수 이어질 수 있기 때문에 $$shape그리고 $shape내가 선호합니다. zz으로 as필드 정말 눈에 띄는 때문이다.
버즈 Moschetti

1
db.test.find( {"shapes.color": "red"}, {_id: 0})

1
스택 오버플로에 오신 것을 환영합니다! 제한적이고 즉각적인 도움을 줄 수있는 코드 스 니펫에 감사드립니다. 적절한 설명은 왜 이것이 문제에 대한 좋은 해결책인지 설명함으로써 장기적인 가치 를 크게 향상시킬 것이며 , 다른 비슷한 질문을 가진 미래 독자들에게 더 유용 할 것입니다. 가정을 포함하여 설명을 추가하려면 답변을 편집하십시오.
sepehr

1

집계 함수를 사용 $project하고 문서에서 특정 객체 필드를 가져옵니다.

db.getCollection('geolocations').aggregate([ { $project : { geolocation : 1} } ])

결과:

{
    "_id" : ObjectId("5e3ee15968879c0d5942464b"),
    "geolocation" : [ 
        {
            "_id" : ObjectId("5e3ee3ee68879c0d5942465e"),
            "latitude" : 12.9718313,
            "longitude" : 77.593551,
            "country" : "India",
            "city" : "Chennai",
            "zipcode" : "560001",
            "streetName" : "Sidney Road",
            "countryCode" : "in",
            "ip" : "116.75.115.248",
            "date" : ISODate("2020-02-08T16:38:06.584Z")
        }
    ]
}

0

이 질문은 9.6 년 전에 제기되었지만 많은 사람들에게 큰 도움이되었습니다. 모든 질문, 힌트 및 답변에 감사드립니다. 여기에 대한 답변 중 하나를 선택하십시오. 다음 방법을 사용하여 부모 문서의 다른 필드를 투영 할 수도 있습니다. 이는 누군가에게 도움이 될 수 있습니다.

다음 문서의 경우 직원 (emp # 7839)이 휴가 기록을 2020 년으로 설정했는지 확인해야합니다. 휴가 기록은 상위 직원 문서 내에 포함 된 문서로 구현됩니다.

db.employees.find( {"leave_history.calendar_year": 2020}, 
    {leave_history: {$elemMatch: {calendar_year: 2020}},empno:true,ename:true}).pretty()


{
        "_id" : ObjectId("5e907ad23997181dde06e8fc"),
        "empno" : 7839,
        "ename" : "KING",
        "mgrno" : 0,
        "hiredate" : "1990-05-09",
        "sal" : 100000,
        "deptno" : {
                "_id" : ObjectId("5e9065f53997181dde06e8f8")
        },
        "username" : "none",
        "password" : "none",
        "is_admin" : "N",
        "is_approver" : "Y",
        "is_manager" : "Y",
        "user_role" : "AP",
        "admin_approval_received" : "Y",
        "active" : "Y",
        "created_date" : "2020-04-10",
        "updated_date" : "2020-04-10",
        "application_usage_log" : [
                {
                        "logged_in_as" : "AP",
                        "log_in_date" : "2020-04-10"
                },
                {
                        "logged_in_as" : "EM",
                        "log_in_date" : ISODate("2020-04-16T07:28:11.959Z")
                }
        ],
        "leave_history" : [
                {
                        "calendar_year" : 2020,
                        "pl_used" : 0,
                        "cl_used" : 0,
                        "sl_used" : 0
                },
                {
                        "calendar_year" : 2021,
                        "pl_used" : 0,
                        "cl_used" : 0,
                        "sl_used" : 0
                }
        ]
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.