본문 바로가기
Infra/MongoDB

MongoDB - (4) - 쿼리

by Inventer 2023. 4. 20.

AND 조건

db.user.find({"username" : "joe", "age" : 27})

 

반환받을 Key 지정

db.users.find({}, {"username" : 1, "email" : 1})

 

아래와 같은 결과를 리턴한다.

{
    "_id" : ObjectId("오브젝트아이디),
    "username" : "joe",
    "email" : "joe@example.com"
}

_id의 경우 설정하지 않더라도 반환된다.

 

db.users.find({"username" : 1, "_id" : 0})

위와 같이 쿼리를 작성하면 _id는 나오지 않는다.

 

 

제약사항

 

mongoDB에서는 c언어와 동일하게 0이 아닌 다른 수는 모두 TRUE로 처리한다.

db.users.find({}, {"fatal_weakness" : -1});
db.users.find({}, {"fatal_weakness" : 3});

위 두 값은 모두 같은 형태를 반환한다.

 

 

위와 같이 "숫자"의 개념으로 접근한다면 JS에서 변수라는 개념을 사용할 수 있을까?

-> 안된다. 

 

  • 쿼리 도큐먼트의 값은 반드시 상수여야한다. (작성한 코드 내에서는 상관 없음)
  • 쿼리 도큐먼트 내 다른 키의 값을 참조할 수 없다.
db.stock.find({"in_stock" : "this.num_sold"}) // 미 동작

 

쿼리 조건

<, <=, >, >= 에 해당하는 비교연산자는 각각 $lt, $lte, $gt, $gte 이다.

햇갈려 죽겠지만 한번 봐본다.

db.users.find({"age" : {$gte : 18, $lte : 30}})

 

18 < age < 30  조건에 age를 가진 도큐먼트만 출력된다.

 

 

주로 날짜에 자주 사용하는데,

start = new Date("01/01/2007")
db.users.find({"create_at" : {$lt : start}})

위와 같은 형식이 가능하다.

 

 

아래는 not equal을 나타내는 연산자이다.

db.users.find({"username" : {$ne : "joe"}})

 

 

OR 쿼리

$in 은 하나의 키를 다양한 값에 비교 가능

$or 은 더 일반적이며 여러 키를 주어진 값과 비교

 

추첨 당첨자를 뽑는 상황에서 당첨번호가 725, 542, 390 일 때

db.raffle.find({"ticket_no" : {$in : [725, 542, 390]}})

 

user_id에 번호 대신 이름을 쓰도록 이전하고 있는 상황

db.users.find({"user_id" : {$in : [12345, "joe"]}})

user_id가 12345 혹은 joe일 때 나온다.

 

 

$nin - not or 이다.

db.raffle.find({"ticket_no" : {$nin : [725, 542, 390]}})

 

$or - ticket_no가 725 거나 winner가 true인 사용자는 어떻게 찾을까?

db.raffle.find({$or : [{"ticket_no" : 725}, {"winner" : true}]})

 

ticket_no가 725, 542, 390 거나 winner가 true인 사용자는 어떻게 찾을까?

db.raffle.find({$or : [{"ticket_no" : {$in : [725, 542, 390]}}, {"winner" : true}]})

 

 

  • 왠만하면 $in을 사용하자. 쿼리 옵티마이저는 $in을 좋아한다.

 

$not

1 ~ 16정도가 있다고 생각하자.

db.users.find({"id_num" : {$mod : [5,1]}})

결과 값은 1, 6, 11, 16 일 것이다.

 

db.users.find({"id_num" : {$not : {$mod : [5,1]}}})

결과 값은 1, 6, 11, 16을 제외한 나머지 수이다.

 

 

아래 쿼리를 날렸을 때 id_num Key가 존재하지 않으면?

db.users.find({"id_num" : {$not : {$mod : [5,1]}}})

모든 것이 다 나온다.

 

 

형특정쿼리

위 결과는 왜 다 나왔을까?

 

null - "존재하지 않음"과 일치하기 때문에 키가 null인 값을 쿼리하면 해당 키를 갖지 않는 모든 행을 반환한다.

 

 

정규표현식

Joe나 joe를 찾고 싶은 경우에,

db.users.find( {"name" : {$regex : /joe/i } })

 

PCRE(Perl Compatible Regular Expression) 라이브러리를 사용하며 PCRE에서 쓸 수 있는 모든 문법은 호환된다.

 

 

배열에 쿼리때리기

db.food.insertOne({"fruit" : ["apple", "banana", "peach"]})

 

아래와 같이 찾아보자.

db.food.find({"fruit" : "banana"})

 

아래와 같은 결과 값을 볼 수 있다.

  {
    _id: ObjectId("6441396c1f91acfcf1fa92c4"),
    fruit: [ 'apple', 'banana', 'peach' ]
  }

 

2개 이상의 배열 요소가 일치하는지 보자, 아래는 도큐먼트를 대략 2차원 배열로 표현한 거다.

[_id][apple][banana][peach]
[_id][apple][kamquat][orange]
[_id][cherry][banana][apple]

 

아래와 같은 명령어를 입력하면 apple과 banana를 갖고 있는 2개의 도큐먼트가 출력될 것이다.

db.food.find({fruid : {$all : ["apple" ,"banana"]}})

 

index로 접근 가능하다.

db.food.find({"fruit.2" : "peach"})

 

$size 쿼리

db.food.find({"fruit" : {$size : 3}})

size = 3인 도큐먼트가 나온다.

굉장히 자주쓰인다.

 

$size는 $gt와 같은 조건절과 같이 사용할 수가 없기에 도큐먼트 자체에 $size 키를 추가시키는 방법이 있다.

db.food.update(criteria, {$push : {"fruit" : "strawberry"}, $inc : {"size" : 1}})

 

 

$slice

criteria는 표기가 귀찮을 때 쓰는 것으로 보여진다(내가 쓴거 아님)

 

아래는 블로그 게시물에서 먼저 달린 댓글 열개를 반환 받는다.

db.blog.posts.findOne(criteria, {"comments" : {$slice : 10}})


-10은 나중에 달린 댓글 열개를 반환 받는다.

 

[23, 10]은 24번쨰 부터 33번째 요소까지 반환한다.

 

 

 

일치하는 배열의 요소 반환

bob이 쓴 댓글을 얻으려면 아래와 같은 쿼리가 가능하다.

db.blog.posts.find({"comments.name" : "bob"}, {"comments.$" : 1})

 

comments 배열에 존재하는 첫 번째 댓글만 반환한다.

 

 

배열 및 범위 커리의 상호작용

아래와 같은 도큐먼트가 있다고 하자.

{"x" : 5}
{"x" : 15}
{"x" : 25}
{"x" : [5, 25]}

 

 

x의 값이 10 ~ 20 사이인 도큐먼트를 찾으려고 할 때 쿼리를 아래와 같이 구성하자.

db.test.find({"x" : {$gt : 10, $lt : 20}})

 

안된다. 실제 출력 값은 아래와 같다.

{"x" : 15}
{"x" : [5, 25]}

위에서 서술한 조건이 각각 적용된 것을 볼 수 있다.

 

elemMatch를 사용하면 이를 해결할 수 있다.

db.test.find({"x" : {$elemMatch : {$gt : 10, $lt : 20}}})

 

 

내장 도큐먼트에 쿼리 때리기

{
	"name" : {
    		"first" : "Joe",
        	"last" : "Schmoe"
	},
   	"age" : 45
}

 

찾아보자.

db.people.find({"name" : {"first" : "Joe", "last" : "Schmoe"}})
  • 서브 도큐먼트와 정확히 일치해야 조회된다.
  • 순서도 일치해야한다.

지만 위 방법은 도큐먼트가 정확히 일치해야하므로 서브 도큐먼트는 다음과 같이 진행하자.

db.people.find({"name.first" : "Joe", "name.last" : "Schmoe"})

 

 

$where

해당 쿼리는 위험하니, 최종사용자가 $where 절을 실행하지 못하도록 해야한다.

왜냐면 해당 where을 사용하는 경우 거의 모든 쿼리를 표현할 수 있기 때문이다.

 

또한 반드시 필요한 경우가 아니라면 속도가 느리니 쓰지 말자.

 

 

커서

DB는 커서를 사용해 find의 결과를 반환한다.

cursor 클래스는 자바스크립트의 iterator 인터페이스를 구현했으므로 .forEach 를 사용할 수 있다.

 

> 는 shell에서 입력창을 의미한다.

 

> var cursor = db.people.find();
> cursor.forEach(function(x) {
...	print(x.name);
...	});


adam
matt
zak

 

위 경우에서는 find를 호출할 때 셸이 DB를 즉시 쿼리하지 않으며 결과를 요청하는 쿼리를 보낼 때 까지 기다린다.

따라서 쿼리하기 전 아래와 같이 옵션을 추가할 수 있다.

 

var cursor = db.foo.find().sort({"x" : 1}.limit(1).skip(10);

 

이렇게 정의한 cursor는 정의 즉시 가져오지 않으며 코드 다음에 cursor.hasNext() 와 같은 함수를 만나면 가져온다.

 

이 때, 서버 왕복 횟수를 줄이기 위해 처음 100개 혹은 4MB 중에 둘 중 작은 것을 가져온다.

첫 번째 결과 셋을 살펴본 후 다음 배치를 반환하도록 요구하며, 모든 결과를 반환해 커서가 소진될 때 까지 계속된다.

 

가장 일반적인 쿼리는

  • 반환 받는 결과 개수를 제한하거나
  • 몇 개의 결과를 건너뛰거나,
  • 결과를 정렬하는 옵션

이 있다.

 

db.c.find().limit(3)

위 처럼 사용이 가능하며 결과가 3개보다 적다면 도큐먼트의 개수만큼 반환한다.

 

db.c.find().skip(3)

처음 3개를 건너뛴다. 3개보다 작으면 아무것도 반환하지 않는다.

 

 

mp3를 검색하고, 가격을 내림차순으로 정렬한다.

db.stock.find({"desc" : "mp3"}).limit(50).skip(50).sort({"price" : -1})

 

위는 책에서 mongoDB의 쿼리를 작성한 것 이지만,

나는 Spring Data mongoDB를 쓸건데 위처럼 작성될 것 같다는 추측이 아주 강력하게 든다.

 

아래는 여러 값이 혼합된 상황에서 비교 순서이다.

  1. 최솟값
  2. null
  3. 숫자(int, long, double, decimal)
  4. 문자열
  5. 객체/도큐먼트
  6. 배열
  7. 이진 데이터
  8. 객체 ID
  9. 불리언
  10. 날짜
  11. 타임스탬프
  12. 정규 표현식
  13. 최댓값

 

DB 성능 높이기

skip은 일단 가져와서 생략된 결과물을 폐기하는 방식이다.

아래와 같이 skip 사용을 피할 수 있다.

 

var latest = null;

while(page1.hasNext()) {
	latest = page1.next();
    display(latest);
}

var page2 = db.foo.find({"data" : {$lt : latest.date}});
page2.sort({"date" : -1}).limit(100);

 

 

서버측 커서

방금 알아본건 클라이언트 커서이고, 서버측 커서도 간단히 알아본다.

 

서버 측에 커서는 리소스를 점유한다. 커서가 더는 가져올 결과가 없거나 클라이언트로부터 종료 요청을 받는다면 점유하고 있던 리소스를 해제한다. 

서버 커서를 종료하는 몇 가지 조건이 있다.

  1. 커서는 조건에 일치하는 결과를 모두 살펴본 후에 스스로 정리한다.
  2. 커서가 클라이언트 측에서 유효 영역을 벗어나면 드라이버는 DB에 메시지를 보내 커서를 종료해도 된다고 알린다.
  3. 사용자가 결과를 아직 다 살펴보지 않았고, 커서가 유효한데 사용자가 10분 동안 활동이 없으면 DB 커서는 종료한다.

따라서 클라이언트가 충돌하거나 버그가 있더라도 DB의 Open된 커서가 수천 개가 될 일은 없다.

'Infra > MongoDB' 카테고리의 다른 글

MongoDB - (6) - 인덱싱2  (0) 2023.04.22
MongoDB - (5) - 인덱싱  (0) 2023.04.21
MongoDB - (3) - CRUD 실습  (0) 2023.04.19
MongoDB - (2) - CRUD와 데이터타입  (0) 2023.04.18
MongoDB - (1) - MongoDB 소개  (0) 2023.04.18

댓글