[MongoDB] Ch4 - Query (쿼리)
$ 조건절을 이용해 범위 쿼리, 셋의 포함 관계, 부등 관계 쿼리 등을 수행한다.
쿼리는 필요할 때마다 도큐먼트 배치을 반환하는 데이터베이스 커서를 반환한다.
커서를 이용해 결과를 몇 개 건너뛰거나, 반환하는 결과 수를 제한하거나, 결과를 정렬하는 등 다양한 메타 연산을 수행한다
4.1 find 소개
db.users.find({"key" : "value"})
1. 반환받을 키 지정
다큐먼트 키/값 정보가 모두 필요없을 때는 아래처럼 작성한다
(네트워크상의 데이터 전송량과 클라이언트 측에서 도큐먼트를 디코딩하는 데 시간과 메모리를 줄여준다)
# username, email 값만 가져오기 (_id 키는 지정하지 않아도 항상 반환)
db.users.find({}. {"username" : 1, "email" : 1})
2. 제약 사항
DB에서 쿼리 도큐먼트 값은 반드시 상수여야 한다
4.2 쿼리 조건
쿼리는 '완전 일치' 외에도 범위, OR절, 부정 조건 등 더 복잡한 조건으로 검색할 수 있다
1. 쿼리 조건절
- $lt(<), $lte(≤), $gt(>), $gte(≥)
- Ex) 18이상 30이하
-
db.user.find({"age" : { "$gte":18, "$lte" : 30}})
-
- Ex) 18이상 30이하
- $ne - (not equal)
2. OR 쿼리
- or 쿼리는 2가지 방법이 있음
- $in - 조건 배열에 값이 하나만 주어지면 바로 일치
- $nin - 배열 내 조건과 일치하지 않는 도큐먼트를 반환
-
db.raffle.find({ "ticket_no" : { "$in" : [725,542,930] } }
-
- $nin - 배열 내 조건과 일치하지 않는 도큐먼트를 반환
- $or - 더 일반적으로 사용
-
db.raffle.find({ "$or" : [ { "ticket_no" : 725 }, { "winner" : true } ] })
-
- $in - 조건 배열에 값이 하나만 주어지면 바로 일치
💡 쿼리 옵티마이저는 "$in"을 더 효율적으로 다룬다
3. $not
- $not은 메타 조건절이며 어떤 조건에도 적용할 수 있다
- (참고) $mod: [3,0] - 첫 번째 값은 나누는 값, 두 번째 값은 나눈 후의 나머지 값
- 3으로 나누면 나머지가 0이되는 값을 모든 검색
4.3 형 특정 쿼리
일부 데이터형은 쿼리 시 형에 특정하게 작동한다
1. null
null은 존재하지 않음과도 일치한다
따라서 키가 없는 경우도 조회가 될 것이다
값이 null인 키만 찾고 있으면, key가 null인 값을 쿼리하고, $exists 조건절을 사용해 null 존재 여부를 확인하자
db.c.find(
{
"z" : {
"$eq" : null,
"$exists" : true
}
}
)
2. 정규 표현식
"$regex"
→ 쿼리에서 패턴 일치 문자열을 위한 정규식 기능을 제공한다
3. 배열에 쿼리하기
db.food.insertOne({"fruit" : ["apple", "banana", "peach"]})
# 다음과 같은 쿼리로 찾을 수 있음
db.food.find({"fruit" : "banana"})
$all 연산자
2개 이상 배열 요소가 일치하는 배열을 찾으려면 $all을 사용
db.food.find({"fruit" : {$all : ["apple", "banana"]}})
$size 연산자
특정 크기의 배열을 쿼리할 수 있다
$slice 연산자
배열 요소의 부분집합을 반환받을 수 있다
# 게시물에 먼저 달린 댓글 10개 반환
db.blog.posts.findOne(criteria, {"comments" : {"$slice" : 10}})
## 반대로 나중에 달린 댓글 10개를 반환
db.blog.posts.findOne(criteria, {"comments" : {"$slice" : -10}})
## 처음 5개를 건너뛰고, 5번째 요소부터 15번째 요소까지 반환
db.blog.posts.findOne(criteria, {"comments" : {"$slice" : [5, 10]}})
일치하는 배열 요소의 반환
# 특정 기준과 일치하는 배열 요소를 반환
db.blog.posts.find({"conmments.name" : "jack"} , {"comments.$" : 1})
배열 및 범위 쿼리의 상호작용
배열을 포함하는 도큐먼트에 범위 쿼리를 할 때, min함수와 max함수를 사용하면 좋다
배열에 대한 $gt, $lt 쿼리의 인덱스 한계는 비효율적이다
4. 내장 도큐먼트에 쿼리하기
내장 도큐먼트에 쿼리할 때는 가능하다면 특정 키로 쿼리하는 방법이 좋다
내장 도큐먼트의 키를 쿼리할 때는 점 표기법을 사용한다
db.people.find({"name.first" : "Joe" , "name.last" : "Schmoe"})
내장 도큐먼트에 키가 없고 배열로 되어있다면, $elemMatch를 사용하여 그룹화할수 있다
db.blog.find({"comments" : { "$elemMatch" : ... {"author" : Joe"}}})
4.4 $where 쿼리
$where 절을 사용해 임의의 자바스크립트를 쿼리의 일부분으로 실행할 수 있다
보안상의 이유로 사용을 제한해야 한다
$where 절은 도큐먼트 내 두 키의 값을 비교하는 쿼리에 가장 자주 쓰인다
$where 쿼리는 일반 쿼리보다 훨씬 느리니 반드시 필요한 경우가 아니면 사용하지 말자
각 도큐먼트는 BSON에서 자바스크립트 객체로 변환되기 때문에 오래걸린다
또한 인덱스도 사용할 수 없다
4.5 커서
데이터베이스 커서를 사용해 find 결과를 반환한다
결과 개수를 제한하거나, 결과 중 몇 개를 건너뛰거나, 여러 키를 조합한 결과를 어떤 방향으로든 정렬하는 등 다양하게 조작할 수 있다
for (i=0; i<100; i++) {
db.collection.insertOne({x:i});
}
var cursor = db.collection.find();
결과를 하나씩 볼 수 있다
컬렉션 내에 무엇이 있는지 보는 데 사용하며, 셸에서 실제 프로그래밍 하는 데는 적합하지 않다
1. 제한, 건너띄기, 정렬
가장 일반적인 쿼리 옵션으로는 반환받는 결과 개수를 제한하거나, 몇 개의 결과를 건너뛰거나, 결과를 정렬하는 옵션이 있다
# 결과 개수를 제한하려면 find 호출에 limit 함수를 연결한다
# 3개의 결과만 반환
db.c.find().limit(3)
#3개를 건너뛰고 나머지 결과를 반환
db.c.find().skip(3)
# sort는 객체를 매개변수로 받는다
# 1(오름차순), -1(내림차순)
db.c.find().sort({username:1, age:-1})
2. 많은 수의 건너띄기 피하기
도큐먼트 수가 적을 때 skip은 무리가 없지만, 결과가 많으면 느려진다
대부분 데이터베이스는 skip을 위해 인덱스 안에 메타데이터를 저장하지만, 몽고DB는 아직 이 기능을 제공하지 않는다
따라서 많은 수의 건너뛰기는 피해야 한다
skip을 사용하지 않고 페이지 나누기
# 아래방법은 사용하지말자
var page1 = db.foo.find().limit(100)
var page2 = db.foo.find().skip(100).limit(100)
var page3 = db.foo.find().skip(100).limit(100)
...
skip을 사용하지않고도 페이징이 가능하다
var page1 = db.foo.find().sort({"date" : -1}).limit(100)
var latest = null;
while (page1.hasNext()) {
latest = page1.next();
display(latest);
}
# 다음 페이지 가져오기
var page2 = db.foo.find({"date" : {"$lt" : latest.date}});
page2.sort({"date" : -1}).limit(100);
랜덤으로 도큐먼트 찾기
컬렉션에서 랜덤으로 요소를 찾아야 한다면 효율적인 방법이 있다
도큐먼트를 입력할 때, 랜덤 키를 별도로 추가하는 방법이다
db.people.insertOne({"name" : "jack", "random" : Math.random()})
db.people.insertOne({"name" : "coding", "random" : Math.random()})
db.people.insertOne({"name" : "jjang", "random" : Math.random()})
# 찾을 때 이렇게 찾을 수 있다
var random = Math.random()
result = db.people.findOne({"random" : {"gt":random}})
종료되지 않는 커서
커서에는 2가지 측면이 있다
지금까지는 클라이언트 커서를 다뤘고, 서버 측에서 보면 커서는 메모리와 리소스를 점유한다
커서가 더는 가져올 결과가 없거나 클라이언트로부터 종료 요청을 받으면 데이터베이스를 점유하고 있던 리소스를 해제한다
그러면 데이터베이스가 리소스를 다른 작업에 사용할 수 있도록 커서도 신속하게 해제해야 한다
서버 커서를 종료하는 몇 가지 조건이 있다
- 커서는 조건에 일치하는 결과를 모두 살펴본 후에는 스스로 정리한다
- 커서가 클라이언트측에서 유효 영역을 벗어나면 드라이버는 데이터베이스에 메시지를 보내 커서를 종료해도 된다고 알린다
- 사용자가 아직 결과를 다 살펴보지 않았고, 커서가 여전히 유효 영역 내에 있더라도 10분 동안 활동이 없으면 데이터베이스 커서는 자동으로 죽는다