이번 포스팅에서는 임베디드 도큐먼트의 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"
}
}
제대로 된 검색 결과를 얻을 수 있습니다.
이번에는 first
와 last
순서를 바꿔 검색해 보면:
> 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)로 매칭 조건을 검색하는 일은 자주 발생하므로 반드시 익혀야 할 주제라 생각됩니다.