안녕하세요. 이번 시간에는 몽구스 쿼리 빌더에 대해 알아보겠습니다. 몽구스같은 ODM을 쓰는 핵심적인 이유이기도 합니다.
ODM이나 ORM이 공통적으로 하는 역할이 있습니다. DB에 상관없이 쿼리 빌더로 쉽게 쿼리를 만들어준다는 것인데요. 몽구스도 ODM이니만큼 쿼리 빌더를 지원합니다. SQL처럼 where절 select 절을 지원해서 nosql에 익숙하지 않으신 분들도 쉽게 접근할 수 있습니다.
User.find({
name: 'zerocho',
birth: { $gt: 20 },
role: { $in: ['owner', 'admin'] }
}, {
name: 1,
birth: 1,
medals: 1,
}).sort({ medals: -1 }).limit(5);
JSON 형식으로 쿼리가 정의되어 있어 장황한 느낌을 줍니다. 쿼리와 프로젝션 부분이 다 객체 형식이라 가독성도 좀 떨어지는 것 같습니다. 위의 쿼리를 아래와 같이 바꿀 수 있습니다.
User.find()
.where('name').equals('zerocho')
.where('birth').gt(20)
.where('role').in(['owner', 'admin'])
.sort('-medals')
.limit(5)
.select('name birth medals')
뭔가 한 눈에 들어오지 않나요? 개인차가 있을 수는 있지만 저는 아래 방식도 괜찮은 것 같습니다. 이제 쿼리 빌더에서 사용할 수 있는 메소드들을 알려드리겠습니다. 전부는 아니고 대표적인 것들입니다.
where
어떤 필드를 조작할지 정하는 쿼리입니다. 뒤에 equals, ne, exists, gt, ... 등의 쿼리를 사용하면 됩니다.
Users.find().where('name')
equals, ne
equals는 일치하는 것, ne는 일치하지 않는 것입니다. exists와 ne를 포함한 쿼리들은 앞에 where이 있으면 where에서 지정한 필드를 업데이트합니다. where이 없다면 쿼리에서 직접 필드를 지정할 수도 있습니다.
Users.find().where('name').equals('zerocho')
Users.find().equals('name', 'zerocho') // 위와 동일한 쿼리
Users.find().ne('name', 'zerocho')
exists
필드 존재 여부를 쿼리합니다.
Users.find().where('name').exists(true)
Users.find().exists('name', true) // 위와 동일한 쿼리
gt, lt, gte, lte
각각 초과(gt), 미만(lt), 이상(gte), 이하(lte)입니다.
Users.find().where('age').gt(20)
Users.find().gt('age', 20);
in, nin, all
in은 배열 안의 값 중 하나라도 일치하는지, nin은 하나도 일치 안 하는지, all은 모두 다 일치하는 지를 쿼리합니다.
Users.find().where('role').in(['owner', 'admin'])
Users.find().in('role', ['owner', 'admin'])
and, or, nor
얘네들은 where를 사용하지 않습니다! 이점 주의하세요. and는 모든 조건을 만족하는지, or는 일부 조건을 만족하는지, nor는 모두 다 만족하지 않는지를 쿼리합니다.
Users.find().all([{ name: 'zerocho' }, { age: 24 }]); // 이름이 zerocho고 나이가 24면
Users.find().or([{ name: 'zerocho' }, { name: 'babo' }]); // 이름이 zerocho거나 babo면
size, mod, slice
얘네들은 조금 특수한 쿼리들입니다. size는 배열 안의 요소 개수가 일치하는지, mod는 나머지가 일치하는지를 쿼리합니다. slice는 쿼리의 특정 부분만 가져옵니다. skip limit과 비슷합니다.
Users.find().where('items').size(2) // items 필드의 배열 요소 개수가 2인지
within, box, circle, geometry, near, intersects, maxDistance
몽고DB는 위치 정보 쿼리 기능이 강력한 것으로 유명하죠. where로 위치 인덱스가 설정된 필드를 지정하고, box, circle 등의 쿼리를 사용합니다.
Users.find().where('loc').within().box()
Users.find().where('loc').within({ box: [[15, 30], [50, 60]] })
Users.find().where('loc').near([50, 60]).maxDistance(30)
distinct
이 기능은 알아두시면 좋습니다. 지정한 필드의 값들에서 중복을 제거한 후 가져옵니다. 예를 들어 10명의 사람의 역할이 각각 user, user, admin, owner, admin, user, user, user, user, user... (헥헥) 이라면 중복을 제거하여 ['user', 'owner', 'admin']이 됩니다.
Users.find().distinct('role') // ['user', 'owner', 'admin']
regex
정규표현식에 일치하는 것을 가져오기 위한 메소드입니다.
Users.find().where('name').regex(/zerocho/)
Users.find().regex('name', /zerocho/)
select
프로젝션(특정 필드만 가져오는 기능)을 위한 메소드입니다. 예를 들어 name과 age만 선택하면 { _id: ..., name: ..., age: ... }
이렇게 결과가 출력됩니다. _id는 빼지 않는 이상 기본 출력됩니다. 특정 필드를 빼고싶다면 앞에 -(빼기)를 붙이면 됩니다.
Users.findOne().select('name age') // { _id: ..., name: ..., age: ... }
Users.findOne().select('-_id name age') // { name: ..., age: ... }
Users.findOne().select({ name: 1, age: 1, _id: 0 }) // 이렇게 객체 형식으로 해도 됩니다.
skip, limit
이것은 자주 보셨을 겁니다. skip은 건너뛰기, limit은 개수 제한을 나타냅니다. limit은 0이면 0개를 가져오는 게 아니라 다 가져옵니다.
Users.find().skip(10).limit(5) // 11~15번째 사람 쿼리
sort
역시 자주 보셨을 겁니다. 정렬이죠.
Users.find().sort('age -medals'); // 나이 오름차순 정렬, 메달 내림차순 정렬
Users.find().sort({ age: 1, medals: -1 }); // 이렇게도 가능합니다.
Users.find().sort({ age: 'asc', medals: 'desc' }); // 이렇게도...
Users.find().sort({ age: 'ascending', medals: 'descending' }); // 이렇게도...
find, findOne, findOneAndRemove, findOneAndUpdate
얘네는 왜 쿼리에 있을까요? 모델의 메소드라고 생각하실 수도 있겠습니다만, 쿼리에서도 사용 가능합니다.
Users.find({ name: 'zerocho' }).find({ age: 24 })
이렇게요. 첫 번째 find는 모델의 메소드이고 두 번째 find는 쿼리의 메소드입니다.
Users.find().where('name').equals('zerocho').findOne()
이런 요상한 것도 가능합니다. 이렇게는 자주 안 쓰지만요.
이상으로 몽구스에 대해 알아보았습니다! 워낙 편리한 기능이 많다보니 몽고DB를 쓰면 꼭 몽구스도 같이 쓰게 되는 것 같습니다. 여러분도 몽구스 한 번 시도해보세요~!