Skip to content

이번 포스팅에서는 임베디드 도큐먼트의 query에 대해 알아보도록 하겠습니다.

임베드 된 도큐먼트에 대한 query 방법은 크게 두 가지로 요약할 수 있습니다:

  • (1) 전체 도큐먼트에 대한 query
  • (2) 개별 키(key)/값(value) 쌍을 이용한 query

상기 두 가지 방법에 대해 각각 알아보도록 하겠습니다.


1. 전체 도큐먼트에 대한 Query

우선, 다음 명령을 통해 임베드 된 도큐먼트를 준비하도록 합니다.

> db.users.drop()
true
> db.users.insert({name: {first: "john", last: "kennedy"}})
> db.users.findOne()
{
    "_id" : ObjectId("52edaa32f97299c19188c2dc"),
    "name" : {
        "first" : "john",
        "last" : "kennedy"
    }
}

name 안에 first(first name: 이름)과 last(last name: 성)이 임베드 되었습니다.

만약 이를 name: {first: "john", last: "kennedy"}로 검색한다면:

> db.users.findOne({name: {first: "john", last: "kennedy"}})
{
    "_id" : ObjectId("52edaa32f97299c19188c2dc"),
    "name" : {
        "first" : "john",
        "last" : "kennedy"
    }
}

제대로 된 검색 결과를 얻을 수 있습니다.

이번에는 firstlast 순서를 바꿔 검색해 보면:

> db.users.findOne({name: {last: "kennedy", first:"john"}})
null

검색 결과가 없는 것으로(null) 결과를 반환합니다.

즉, 임베드된 도큐먼트 검색은 순서에 의존적(order-sensitive)인 것을 알 수 있습니다.

이번에는 name: {first: "john"}만으로 검색해 보겠습니다:

> db.users.findOne({name: {first: "john"}})
null

검색 결과가 없습니다.

마찬가지로 name: {last: "kennedy"}로 검색하더라도 검색 결과는 "null"로 반환될 것입니다.

결론적으로, "전체 도큐먼트에 대한 query"는 말 그대로 임베드 된 도큐먼트에 대한 내용 전체를 그대로 검색해야만 올바른 검색 결과를 얻을 수 있습니다.

만약 name에 middlename "fitzgerald"를 추가했다고 하면, 이제 앞의 검색방법으로는 원하는 검색결과를 얻을 수 없을 것입니다.

이러한 단점을 극복하기 위해 MongoDB는 개별 key/value 쌍을 이용한 query인 $elemMatch를 제공합니다.


2. 개별 키/값 쌍을 이용한 query

이제 query를 엘리먼트 단위로 검색할 수 있는 $elemMatch를 살펴보겠습니다.

앞서 임베드된 도큐먼트의 전체 내용으로 검색하는 것에 대한 불편함을 살펴보았습니다. 임베드된 도큐먼트의 엘리먼트 key로 검색할 수 있는 방법은 우선 도큐먼트 임베드 시 구조를 array 형태로 구성합니다:

우선 다음과 array 형태의 도큐먼트를 임베드 합니다:

db.schools.drop()

db.schools.insert(
    [
        {
             _id: 1,
             zipcode: 63109,
             students: [
                          { name: "john", school: 102, age: 10 },
                          { name: "jess", school: 102, age: 11 },
                          { name: "jeff", school: 108, age: 15 }
                       ]
        },
        {
             _id: 2,
             zipcode: 63110,
             students: [
                          { name: "ajax", school: 100, age: 7 },
                          { name: "achilles", school: 100, age: 8 },
                       ]
        },
        {
             _id: 3,
             zipcode: 63109,
             students: [
                          { name: "ajax", school: 100, age: 7 },
                          { name: "achilles", school: 100, age: 8 },
                       ]
        },
        {
             _id: 4,
             zipcode: 63109,
             students: [
                          { name: "barney", school: 102, age: 7 },
                       ]
        }
    ]
)

다음과 같은 내용을 확인할 수 있을 것입니다:

> db.schools.find()
{ "_id" : 1, "zipcode" : 63109, "students" : [  {   "name" : "john",    "school" : 102,     "age" : 10 },   {   "name" : "jess",    "school" : 102,     "age" : 11 },   {   "name" : "jeff",    "school" : 108,     "age" : 15 } ] }
{ "_id" : 2, "zipcode" : 63110, "students" : [  {   "name" : "ajax",    "school" : 100,     "age" : 7 },    {   "name" : "achilles",    "school" : 100,     "age" : 8 } ] }
{ "_id" : 3, "zipcode" : 63109, "students" : [  {   "name" : "ajax",    "school" : 100,     "age" : 7 },    {   "name" : "achilles",    "school" : 100,     "age" : 8 } ] }
{ "_id" : 4, "zipcode" : 63109, "students" : [  {  "name" : "barney",  "school" : 102,  "age" : 7 } ] }

이제 zipcode가 63109인 것 중, school이 102와 요소 매칭되는 도큐먼트를 검색해 보겠습니다:

> db.schools.find( { zipcode: 63109 }, { students: { $elemMatch: { school: 102 } } } )
{ "_id" : 1, "students" : [  {  "name" : "john",  "school" : 102,  "age" : 10 } ] }
{ "_id" : 3 }
{ "_id" : 4, "students" : [  {  "name" : "barney",  "school" : 102,  "age" : 7 } ] }

zipcode가 63109인 것은 _id: 1, _id: 3, _id: 4로 모두 3개이며, school이 102인 것은 _id: 1, _id: 4이므로 _id: 3에 대해서는 아무 내용이 표시되지 않았으며 나머지 두 도큐먼트에 대한 내용이 출력되었습니다.

이번에는 더 세밀하게 앞서 검색 조건에 덧붙여 나이(age)가 8살 이상인 학생이 포함된 도큐먼트를 검색해 보겠습니다:

> db.schools.find( { zipcode: 63109 }, { students: { $elemMatch: { school: 102, age: { $gt: 10} } } } )
{ "_id" : 1, "students" : [  {  "name" : "jess",  "school" : 102,  "age" : 11 } ] }
{ "_id" : 3 }
{ "_id" : 4 }

검색조건에 맞는 올바른 검색결과를 얻을 수 있을 것입니다.

마지막으로, zipcode가 63109인 것 중 school이 102와 요소 매칭되는 도큐먼트에 포함된 학생 중 나이가 어린 순으로 분류(sort)해 보겠습니다:

> db.schools.find({ zipcode: 63109 }, { students: { $elemMatch: { school: 102 } } }).sort( { "students.age": 1 } )
{ "_id" : 3 }
{ "_id" : 4, "students" : [  {  "name" : "barney",  "school" : 102,  "age" : 7 } ] }
{ "_id" : 1, "students" : [  {  "name" : "john",  "school" : 102,  "age" : 10 } ] }

지금까지 임베드 된 도큐먼트에 대한 query 방법에 대해 알아 보았습니다. 비정형구조의 데이터베이스(NoSQL)에서 도큐먼트가 임베드 되는 경우가 많으며 임베드 된 도큐먼트의 요소(elemenet)로 매칭 조건을 검색하는 일은 자주 발생하므로 반드시 익혀야 할 주제라 생각됩니다.