Computer >> 컴퓨터 >  >> 프로그래밍 >> MongoDB

MongoDB에서 하위 문서로 하위 문서를 필터링하는 방법 ($unwind 활용)

MongoDB에서 배열 형태의 하위 문서(서브 도큐먼트) 안에 또 다른 하위 문서가 중첩되어 있을 때, 특정 조건에 맞는 요소만 골라내야 하는 경우가 종종 있습니다. 이럴 때는 aggregate() 메서드와 $unwind 연산자를 함께 사용하면 됩니다. 이번 글에서는 실제 예제를 통해 그 과정을 단계별로 살펴보겠습니다.

1. 샘플 데이터 준비하기

먼저 데모용 컬렉션을 만들고 문서를 삽입합니다. 아래 예제는 details1 배열 안에 details2 배열이 한 번 더 중첩된 구조를 가지고 있습니다.

> db.demo583.insert([
...    {
...       "details1" : [
...          {
...             "details2" : [
...                {
...                   "isMarried" : true,
...                   "Name" : "Chris"
...                },
...                {
...                   "isMarried" : true,
...                   "Name" : "Bob"
...                }
...             ]
...          },
...          {
...             "details2" : [
...                {
...                   "isMarried" : false,
...                   "Name" : "Chris"
...                },
...                {
...                   "isMarried" : true,
...                   "Name" : "Mike"
...                }
...             ]
...          }
...       ]
...    }
... ]);
BulkWriteResult({
   "writeErrors" : [ ],
   "writeConcernErrors" : [ ],
   "nInserted" : 1,
   "nUpserted" : 0,
   "nMatched" : 0,
   "nModified" : 0,
   "nRemoved" : 0,
   "upserted" : [ ]
})

2. 저장된 문서 확인하기

find() 메서드를 사용하면 컬렉션에 저장된 모든 문서를 조회할 수 있습니다.

> db.demo583.find();

위 명령을 실행하면 다음과 같은 결과가 출력됩니다.

{ "_id" : ObjectId("5e91d3c4fd2d90c177b5bcc1"), "details1" : [
   { "details2" : [
      { "isMarried" : true, "Name" : "Chris" },
      { "isMarried" : true, "Name" : "Bob" }
   ] },
   { "details2" : [
      { "isMarried" : false, "Name" : "Chris" },
      { "isMarried" : true, "Name" : "Mike" }
   ] }
] }

3. 하위 문서를 조건으로 필터링하는 쿼리

이제 하위 문서의 값을 기준으로 다시 하위 문서를 필터링하는 쿼리를 작성해 보겠습니다. 핵심 흐름은 다음과 같습니다. 먼저 $match로 조건에 맞는 문서를 걸러내고, $unwind로 중첩 배열을 개별 요소로 펼친 뒤, 마지막에 다시 $match를 적용해 원하는 하위 문서만 남기는 방식입니다.

> var q= [
...    {
...       "$match": {
...          "details1.details2.isMarried": true,
...          "details1.details2.Name": "Chris"
...       }
...    },
...    {
...       "$unwind": "$details1"
...    },
...    {
...       "$unwind": "$details1.details2"
...    },
...    {
...       "$match": {
...          "details1.details2.isMarried": true,
...          "details1.details2.Name": "Chris"
...       }
...    }
... ];
> db.demo583.aggregate(q).pretty();

4. 실행 결과

집계 파이프라인을 실행하면 isMarried 값이 true이면서 Name이 "Chris"인 하위 문서만 정확하게 추출됩니다.

{
   "_id" : ObjectId("5e91d3c4fd2d90c177b5bcc1"),
   "details1" : {
      "details2" : {
         "isMarried" : true,
         "Name" : "Chris"
      }
   }
}

쿼리 동작 원리 정리

  • 첫 번째 $match : 최상위 문서 수준에서 details1.details2 경로에 조건(isMarried: true, Name: "Chris")이 하나라도 존재하는 문서만 통과시킵니다.
  • $unwind : details1 배열과 details2 배열을 차례로 개별 문서로 분해합니다. 이 과정을 거치면 중첩된 하위 문서 하나하나가 독립적인 레코드가 됩니다.
  • 두 번째 $match : 분해된 개별 하위 문서에 다시 조건을 검사하여, isMarried가 true이고 Name이 "Chris"인 요소만 최종 결과로 남깁니다.

이처럼 $unwind로 배열을 펼친 후 $match를 다시 적용하면 부모 문서가 아니라 하위 문서 자체를 기준으로 정확하게 필터링할 수 있습니다. 중첩 배열 구조에서 필요한 데이터만 추출할 때 매우 유용한 패턴이니 꼭 기억해 두세요.