목차
0. MongoDB Query
- Query는 컬렉션에서 도큐먼트 서브셋(빈 컬렉션부터 컬렉션 전체까지)을 반환
- find()의 첫 매개변수에 따라서 어떤 도큐먼트를 가져올지 결정
- 빈 쿼리 도큐먼트(
{}) 일 경우 컬렉션 A 내의 모든 도큐먼트를 반환
db.A.find()
1. find() or findOne()
1) 쿼리 도큐먼트에 key/value 쌍으로 검색을 할 수 있다.
//조건 1
db.users.findOne({_id: ObjectId("66e386c301b9bf745668c185")})
//조건 1 And 조건 2
db.users.find({username: 'devPaik', age: 15})
2) 반환받을 키 지정
도큐먼트 내 키/값 정보가 모두 필요하지 않을 때가 있으므로 find 또는 findOne의 두 번째 매개변수에 원하는 키를 지정
db.users.find({}, {username:1, email: 1})
_id 반환 제외
db.users.find({}, {username:1, _id: 0})
2. 쿼리조건
2-1. 쿼리 조건 절
1) 비교연산자
| < | $lt |
|---|---|
| ≤ | $lte |
| > | $gt |
| ≥ | $gte |
| <> or ≠ | $ne |
| in | $in |
| not in | $nin |
| or | $or |
| not | $not |
- age가 10이상 30이하인 도큐먼트
db.users.find({age: {$gte:10,$lte:30} })
$ne (모든 데이터형에 사용): devPaik이 아닌 사용자를 찾을 경우
db.users.find({username: {$ne: "devPaik"}})
$in: 하나의 키를 다양한 값과 비교하여 일치할 경우
db.users.find({age: {$in: [25, 20]}})
$nin: 하나의 키를 다양한 값과 비교하여 일치하지 않을 경우
db.users.find({age: {$nin: [25, 20]}})
$or또는 or 조건 절 사용
//20세 또는 [hogil, kim] 이름을 가지고 있는 document
db.users.find({$or: [ {age: 20}, {username: {$in: ['hongil','kim']} } ] });
같은 데이터로 조건을 비교해 보기
앞의 db.users가 비어 있으면 결과를 해석하기 어렵다. 다음은 실습용 데이터베이스에만 넣는 작은 예제다. 운영 컬렉션에서 deleteMany나 무조건 삽입을 반복하지 않는다.
use query_lab
db.users.insertMany([
{ username: "min", age: 20, status: "active" },
{ username: "jin", age: 25, status: "active" },
{ username: "sol", age: 30, status: "paused" },
{ username: "no_age", status: "active" }
])
db.users.find({ status: "active", age: { $gte: 20, $lt: 30 } },
{ _id: 0, username: 1, age: 1 })
결과는 min과 jin 두 도큐먼트의 username, age만 포함한다. 같은 객체의 status와 age는 AND 조건이다. $gte는 20을 포함하고 $lt는 30을 제외한다. 두 번째 인수인 projection에서 _id는 기본 포함되므로 0으로 명시해 제외했다. findOne()은 조건에 맞는 문서 하나를 반환하지만 정렬을 지정하지 않은 첫 문서가 업무상 최신이라는 보장은 없다. 최신 한 건이 필요하면 명시적인 정렬 기준과 동점 처리 필드를 지정한다.
db.users.find({ status: "active" }, { _id: 0, username: 1, age: 1 })
.sort({ age: -1, username: 1 }).limit(2)
이 예에서 age가 없는 문서의 정렬 위치는 자료형 비교 규칙에 영향을 받으므로, 실무에서는 필수 필드 검증 또는 $exists 조건을 검토한다. skip과 limit으로 깊은 페이지를 계속 넘기면 앞의 결과를 건너뛰는 비용이 커질 수 있다. 큰 목록에서는 정렬 키를 이용한 커서 기반 페이지 이동을 고려한다.
누락된 필드와 부정 조건
원문 $ne와 $nin은 단순한 “다른 값” 직관과 달리 필드가 없는 도큐먼트도 선택할 수 있다. 예를 들어 db.users.find({ age: { $ne: 20 } })은 age가 없는 no_age도 결과에 들어갈 수 있다. “나이가 있고 20이 아닌 사용자”가 필요하면 존재 조건을 함께 쓴다.
db.users.find({ age: { $exists: true, $ne: 20 } })
db.users.find({ $or: [{ age: 20 }, { username: { $in: ["jin", "sol"] } }] })
$or의 두 조건 중 하나라도 만족하면 선택된다. 하나의 필드에 여러 비교 조건을 적용할 때는 { age: { $gte: 20, $lte: 30 } }처럼 한 필드 객체에 넣는다. 사용자에게 받은 필드명이나 쿼리 객체를 그대로 DB에 전달하면 의도하지 않은 연산자 사용이 가능하므로, API에서는 허용할 필터와 자료형을 정해 변환해야 한다.
flowchart LR Q[필터 객체] --> M[조건에 맞는 도큐먼트 선택] M --> S[정렬] S --> L[제한] L --> P[필요한 필드 투영] P --> R[애플리케이션 응답]
느린 조회를 살피는 순서
db.users.createIndex({ status: 1, age: -1 })
db.users.find({ status: "active", age: { $gte: 20 } })
.sort({ age: -1 }).explain("executionStats")
실행 통계에서 읽은 인덱스 키(totalKeysExamined), 읽은 도큐먼트(totalDocsExamined), 반환한 수(nReturned)를 비교한다. 10건만 반환하는데 수십만 도큐먼트를 읽는다면 인덱스·조건·정렬의 조합을 다시 검토한다. 다만 작은 컬렉션에서는 전체 스캔이 더 경제적일 수 있고, 인덱스는 쓰기 비용과 저장 공간을 늘린다. 복합 인덱스의 필드 순서는 자주 쓰는 필터와 정렬 패턴을 기준으로 실제 explain 결과를 비교해 정한다. projection으로 전송량을 줄일 수 있지만 색인된 필드만 읽는 covered query가 되려면 _id 포함 여부까지 조건이 맞아야 한다.
| 기대와 다른 결과 | 먼저 확인 |
|---|---|
find() 결과가 너무 많음 | 필터가 {}인지, $or 범위가 넓은지 |
_id가 계속 보임 | projection의 _id: 0 |
$ne 결과에 빈 필드 포함 | $exists: true 필요 여부 |
| 조회가 느림 | explain("executionStats")와 인덱스·정렬·반환 건수 |
참고: mongosh 조회, 필드 선택, 느린 쿼리 분석, 쿼리 최적화.