[4.2] 쿼리 / 쿼리조건(Type specific)

2022. 7. 19. 02:46책/몽고DB 완벽 가이드

3. 형 특정 쿼리(type-specific query)

몽고 DB는 도큐먼트 내에서 다양한 데이터타입을 사용할 수 있다. 그리고 일부 데이터타입은 쿼리 시 타입에 특정하게 작용한다.

 

3.1 null
null은 키의 값이 null인 경우와 키가 '존재하지 않는' 경우 모두를 뜻한다. 따라서 값이 null인 키만 찾고 싶으면 추가적인 조건($exist)을 사용해야 한다.

db.vector
{ x: 5 }
{ x: 3, y: 4 }
{ x: 4, y: 3 }
{ x: null, y: 5 }
{ x: null, y: null }

y값이 null인 도큐먼트를 찾고싶다고 치자
그냥 null로 검색해버리면 y키가 없는 도큐먼트도 내뿜는다

db.vector.find( { y: null } )
{ x: 5 }
{ x: null, y: null }

따라서 y값이 null인 도큐먼트를 찾고싶다면
아래처럼 $exist를 이용하자

db.vector.find( { y: { $eq : null, $exist : true } } );
{ x: null, y: null }


3.2 $regex (정규 표현식 / Query Operator > Evaluation )

{ <field>: { $regex: /pattern/, $options: '<options>' } }
{ <field>: { $regex: 'pattern', $options: '<options>' } }
{ <field>: { $regex: /pattern/<options> } }
  • 문자열 패턴이 일치하는 도큐먼트를 찾는다.
  • null과 유사하게 정규 표현식 그 자체와 일치하는 도큐먼트 또한 쿼리가 가능하다
    (물론 DB에 정규표현식을 저장해두는 일은 거의 없다)
  • 몽고 DB는 정규 표현식 일치에 펄 호환 정규 표현식(PCRE; Perl Compatible Regular Expression) 라이브러리를 쓴다. JS 정규표현식도 PCRE로 돌아가므로 JS 방식 그대로 갖다 쓰면 된다.
  • 옵션은 JS 정규표현식의 플래그와 같다고 보면 된다
이름이 A로 시작하는 챔피언을 찾아보자
db.champions
{ name : Ahri, position : [mid] }
{ name : Akali, position : [mid, top] }
{ name : Amumu, position : [sup, jungle] }
{ name : Yaso, position : [science] }

db.champions.find({ name : { $regex : /^A/ } }); 
아리, 아칼리, 아무무가 나옴

이름이 Y로 시작하는 챔피언을 찾아보자

db.champions.find({ name : /^Y/ });
그저 야이언스...


정규표현식이 값으로 입력된 컬렉션...
진짜로 이런 정규표현식을 DB에 넣어둔다고? ㄹㅇ??

db.garbages
{ idx : 1, content : /joi/ }
{ idx : 2, content : /yaso/ }
{ idx : 3, content : /yone/ }

어쨌든
db.find({ content: /yaso/ });
{ idx : 2, content : /yaso/ }


4.3 Array
배열 요소의 쿼리는 스칼라 쿼리와 같은 방식으로 동작하도록 설계됐다. 

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

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


◎ $elemMatch 

{ <field>: { $elemMatch: { <query1>, <query2>, ... } } }
  • 배열內 조건에 맞는 요소가 하나라도 있으면 해당 도큐먼트를 반환한다
  • 논리상 OR 연산자와 동일한 효과를 갖는다
  • 쿼리조건이 하나이며, $not/$ne를 포함하지 않는 쿼리라면 $elemMatch를 굳이 쓸 필요는 없다(싱글 쿼리 조건과 동일하다)
  • $where 표현식의 구체화 용도로 사용할 순 없다
최근 5판내 누적딜량이 1~2만 사이였던 적이 있는지 찾아보자

db.records
{ id : '체강미드', record : [ 0.8, 2.5, 4.2, 0.3, 0.7 ] }
{ id : '유미안주면던짐', record : [ 0.3, 0.1, 0.0, 0.8, 1.7 ] }
{ id : '탑신병자', record : [ 2.8, 1.7, 3.2, 0.3, 0.9 ] }


db.records.find({ record : { $elemMatch : { $gte : 1.0, $lte : 2.0 } } });
{ id : '유미안주면던짐', record : [ 0.3, 0.1, 0.0, 0.8, 1.7 ] }
{ id : '탑신병자', record : [ 2.8, 1.7, 3.2, 0.3, 0.9 ] }
// '체강미드'는 누적딜이 1~2만 사이였던 적이 한번도 없으므로 제외된다


◎ $all

{ <field>: { $all: [ <value1> , <value2> ... ] } }
  • 찾고자 하는 값을 모두 포함하는 도큐먼트를 찾는다(순서는 상관X)
  • 논리상 AND연산자처럼 행동하며, $and를 사용한 것과 동일한 효과를 갖는다
  • 배열 내 특정 요소를 쿼리하고 싶다면 key.index 구문을 이용해 순서를 지정한다
  • 만약 완벽하게 일치하는 배열을 찾고 싶다면 $all 기호를 빼주면 된다
db.food
{ "fruit" : [ "apple", "banana", "peach" ] }
{ "fruit" : [ "apple", "kumquat", "orange" ] }
{ "fruit" : [ "cherry", "banana", "apple" ] }

1) 만약 apple과 banana가 포함된 도큐먼트를 찾고싶다면?
db.food.find({ fruit: { $all : [ "apple", "banana" ] } });
{ "fruit" : [ "apple", "banana", "peach" ] }
{ "fruit" : [ "cherry", "banana", "apple" ] }

2) 만약 [ "apple", "banana" ] 배열과 정확하게 동일한
   도큐먼트를 찾고싶다면?
db.food.find({ fruit: [ "apple", "banana" ] });
// 그런거 없다 이녀석아

3) 만약 배열의 두번째 요소가 바나나인 경우를 찾고싶다면?
db.food.find({ "fruit.1" : "banana" });
{ "fruit" : [ "apple", "banana", "peach" ] }
{ "fruit" : [ "cherry", "banana", "apple" ] }

 

◎ $size

{ <field>: { $size: N  } }
  • 특정 크기의 배열을 쿼리한다
  • 다른 $조건절이나 $addToSet과는 같이 못써서 구려보이지만 의외로 자주 쓰인다
  • 근데 그냥 도큐먼트에 size 키를 따로 넣어서 관리하면 훨씬 더 쉽고 빠르다
db.users
{ "user" : "쓰레기", "champs" : [ "아리", "갈리오", "빅토르" ] }
{ "user" : "벌레", "champs" : [ "케이틀린", "코그모" ] }
{ "user" : "트롤", "champs" : [ "아무무", "쉔", "오른" ] }

챔프폭이 3개인 유저를 찾고싶다면?
db.users.find({ champs : { $size : 3 } })
{ "user" : "쓰레기", "champs" : [ "아리", "갈리오", "빅토르" ] }
{ "user" : "트롤", "champs" : [ "아무무", "쉔", "오른" ] }

근데 size키를 따로 관리한다면???
db.users
{ user : "쓰레기", champs : [ "아리", "갈리오", "빅토르" ], size: 3 }
{ user : "벌레", champs : [ "케이틀린", "코그모" ], size: 2}
{ user : "트롤", champs : [ "아무무", "쉔", "오른" ], size: 3 }

다른 연산자와 결합해서 더 복잡한 연산을 쉽고 빠르게 검색할 수 있음
오히려 좋아
db.users.find({ size : { $gt : 3 } });

 

◎ $slice (projection operator)

db.collection.find( <query>, { <arrayField> : { $slice: <number to skip?>, <number to return> } }
  • 프로젝션 연산자로 지정한 갯수만큼 배열 요소를 쿼리한다
  • 앞/뒤 어디서부터 몇개나 쿼리할건지 지정할 수 있다
  • 몇개나 건너뛸 것인지도 지정할 수 있다
db.blog.posts
{
  "title" : "내 첫뻔째 게시글", 
  "date" : "2022-07-26", 
  "comments" : [ 
    { "user" : "ㅇㅇ", "content" : "뉴비 ㅎㅇ" }, 
    { "user" : "ㄴㄴ", "content" : "지금 뉴비가 왔다고? 돔황챠" }, 
    { "user" : "ㅂㅂ", "content" : "뉴비여 어찌하여 이 똥통에 찾 아왔는가" } 
  ]
}

맨 마지막에 남긴 댓글을 찾고싶다면?
db.blog.posts.find({ date: '2022-07-26'}, { comments: { $slice: -1 } } );
{ 
  "title" : "내 첫뻔째 게시글", 
  "date" : "2022-07-26", 
  "comments" : [ 
    { "user" : "ㅂㅂ", "content" : "뉴비여 어찌하여 이 똥통에 찾아왔는가" } 
  ] 
}

그런데 댓글만 읽고 싶은데 글 제목이랑 날짜랑 다 나와버린다
진짜로 댓글'만' 가져오는 방법은 바로 뒤에서 알아보자

 

◎ 일치하는 첫 번째 배열 요소($)

  •  Array.prototype.find( )함수와 비슷한 역할을 한다
  • 특별한 함수가 따로 있는건 아니고 $연산자를 활용하는 것 뿐이다
댓글 작성자가 ㅇㅇ인 댓글만 가지고와보자
대단한 방법이 있는건 아니고 그냥 잡기술임;;

db.blog.posts.find({ "comments.user" : "ㅇㅇ" }, { "comments.$" : 1 });
{ "comments" : [ { "user" : "ㅇㅇ", "content" : "뉴비 ㅎㅇ" } ] }

 

◎ 범위 쿼리

  • 도큐먼트內 한 필드의 자료형은 하나의 타입을 유지하는게 일반적인 관례이다
  • 그러나 한 필드의 자료형이 스칼라이거나 배열일 수도 있는데(애초에 잘 설계한 것인지 의심되지만)
  • 이 상황에선 min, max함수를 사용할 수 있다
  • 단, 몽고쉘 전용 함수이며 인덱스 설정이 필요하므로 나중에 필요하면 찾아보고 쓰도록 하자 ㅅㄱ