Database/MongoDB

MongoDB - 4. Qeury

FreeEnd 2021. 10. 21. 22:22
반응형

 이전까지는 샘플로 Query 를 작성해 간단한 데이터 조회만 담당 했다면, 이번에는 다양한 조회 옵션을 이용해 조금더 심도 있게 Query 를 조회 해 보자.

 

FIND - 기본 조회 METHOD

 

 조회된 Document 를 여러개 출력할 것인지, 단 하나만 출력할것인지에 따라 find 와 findOne 을 골라 사용할 수 있다.

findOne Document 를 반환함
조회된 결과중 하나의 객체만 조회
여러개일 경우 자연정렬(삽입순서) 대로 1개의 데이터만 출력
find Cursor 를 반환함
조회된 모든 객체를 조회

findOne 의 경우 하나의 Document 만 출력되므로 그대로 사용하면 되지만, find 의 경우 조회된 Document 들의 위치값을 반환하므로 커서를 반복해서 조회된 Document 를 조회 해야 한다.

 

Cursor - 조회 결과 

참고 : https://docs.mongodb.com/manual/reference/method/js-cursor/

 커서는 여러 Document 집합의 위치 값이다. 이 커서가 제공하는 Method 를 이용하면 다양한 처리를 할 수 있다.

 

cursor.limit(N)

limit 는 조회된 개수를 N 개만 반환한다.

> db.item.find({}).limit(1)
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001" }

 

cursor.count()

count 는 조회된 개수를 반환한다.

> db.item.find({}).count()
7

 

cursor.sort()

sort 는 조회된 결과를 정렬한다. sort 의 인자로 정렬하고자 하는 key 와 오름차순일경우 1, 내림 차순일 경우 -1을 전달한다.

> db.item.find({}).sort({itemId:-1})
{ "_id" : ObjectId("61642aa8b177ff22016eb523"), "itemId" : "1000000000007" }
{ "_id" : ObjectId("61642aa6b177ff22016eb522"), "itemId" : "1000000000006" }
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }
{ "_id" : ObjectId("61642aa3b177ff22016eb520"), "itemId" : "1000000000004" }
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002" }
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001" }

 

 

cursor.forEach()

forEach 는 조회된 Documnt 를 순차적으로 작성된 function 을 반복 수행한다.

> db.item.find({}).forEach(
...     function(docu){
...         print(docu._id);
...     }
... )
ObjectId("61642a98b177ff22016eb51d")
ObjectId("61642a9fb177ff22016eb51e")
ObjectId("61642aa2b177ff22016eb51f")
ObjectId("61642aa3b177ff22016eb520")
ObjectId("61642aa5b177ff22016eb521")
ObjectId("61642aa6b177ff22016eb522")
ObjectId("61642aa8b177ff22016eb523")

 

cursor.skip(N)

skip 은 조회된 데이터중, 순서대로 지정된 N 개의 수만큼 건너띄고 그 다음 Document 부터 커서를 지정 한다. 결국 지정된 수만큼 건너 띄어서 Documnt 를 반환하게 된다.

> db.item.find({}).skip(2)
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }
{ "_id" : ObjectId("61642aa3b177ff22016eb520"), "itemId" : "1000000000004" }
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }
{ "_id" : ObjectId("61642aa6b177ff22016eb522"), "itemId" : "1000000000006" }
{ "_id" : ObjectId("61642aa8b177ff22016eb523"), "itemId" : "1000000000007" }

 

cursor method 중, limit 와 skip 을 이용하면, 게시판의 페이지 기능도 개발 가능하다.

예를 들어 1페이부터 시작하는 X 개씩 출력하는 게시판 기능중, m 번째 페이지를 출력하기 위해서는

 

SKIP SIZE = X * (M - 1)

PAGE SIZE = X

 

db.item.find({}).skip(SKIP SIZE).limit(PAGE SIZE)

이렇게 조회 하면 된다.

 

 샘플로 조회 해보자면.. 7개의 상품 뿐이 없기떄문에, 2개씩 조회하되, 3번째 페이지를 조회 하라의 샘플이다.

> db.item.find({}).skip(2 * (3-1) ).limit(2)
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }
{ "_id" : ObjectId("61642aa6b177ff22016eb522"), "itemId" : "1000000000006" }

1페이지 1,2 상품 / 2페이지 3,4 상품 이 제외되고 5,6 번 상품만 조회 결과로 반환 되었다.

 

이때 skip 과 limit 가 순서대로 전달 된것이 아닌, 조회 조건으로 함께 전달된다.  순서대로 전달 되었다면 limit 와 skip 이 뒤바꾼다면 결과는 다를것이다. 하지만 결과는 같다.

> db.item.find({}).limit(2).skip(2 * (3-1) )
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }
{ "_id" : ObjectId("61642aa6b177ff22016eb522"), "itemId" : "1000000000006" }

 

이렇게 cusor 에 method 를 중첩으로 전달 하는 패턴은 메서드체이닝(method chaining) 이라고 부른다. 



 

Projection - 조회 결과 반환 항목 설정

 많은 수의 키를 가지고 있는 Document 의 경우, 매번 사용하지도 않는 키를 포함하여 모두 조회 할 경우, 낭비가 될수  있다. 이때, 조회 하고자 하는 키만 지정해 결과를 return 할 수 있다.

 find method 의 두번째 인자에, 조회하고자 하는 key 와 함께 1 을 입력하면 해당 값만 출력 가능하며, 0일 경우, 제외도 가능하다. 물론 document 형태로 어려 조건을 입력 할 수 있다.

> db.item.find({}, {_id : 1})
{ "_id" : ObjectId("61642a98b177ff22016eb51d") }
{ "_id" : ObjectId("61642a9fb177ff22016eb51e") }
{ "_id" : ObjectId("61642aa2b177ff22016eb51f") }
{ "_id" : ObjectId("61642aa3b177ff22016eb520") }
{ "_id" : ObjectId("61642aa5b177ff22016eb521") }
{ "_id" : ObjectId("61642aa6b177ff22016eb522") }
{ "_id" : ObjectId("61642aa8b177ff22016eb523") }

> db.item.find({}, {_id : 0})
{ "itemId" : "1000000000001" }
{ "itemId" : "1000000000002" }
{ "itemId" : "1000000000003" }
{ "itemId" : "1000000000004" }
{ "itemId" : "1000000000005" }
{ "itemId" : "1000000000006" }
{ "itemId" : "1000000000007" }

 단 _id 는 기본 항목으로 itemId 만 조회 하기 위해 조건을 넘겨도 _id 는 함께 반환된다. 떄문에 _id를 반환하고 싶지 않다면 _id 를 명시적으로 제외 해야 한다.

> db.item.find({}, { itemId : 1})
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001" }
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002" }
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }
{ "_id" : ObjectId("61642aa3b177ff22016eb520"), "itemId" : "1000000000004" }
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }
{ "_id" : ObjectId("61642aa6b177ff22016eb522"), "itemId" : "1000000000006" }
{ "_id" : ObjectId("61642aa8b177ff22016eb523"), "itemId" : "1000000000007" }

 

 

정규표현식 검색

 Mongo DB 에서도 LIKE 검색이 가능하다. 단, 정규 표현식으로 조회 해야 하며, 앞 부분이 고정되어 있는 조회 조건은 INDEX 사용이 가능하지면, 앞부분이 정규표현식으로 구현되면, INDEX 를 사용하지 않기때문에 검색 속도가 늦어질 수 밖에 없다.

> db.item.find({itemId : /^10/})
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001" }
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002" }
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }
{ "_id" : ObjectId("61642aa3b177ff22016eb520"), "itemId" : "1000000000004" }
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }
{ "_id" : ObjectId("61642aa6b177ff22016eb522"), "itemId" : "1000000000006" }
{ "_id" : ObjectId("61642aa8b177ff22016eb523"), "itemId" : "1000000000007" }

 

 또, $regex 연산자도 제공한다. 예를 들어 다음은 2 와 3이라는 문자열을 포함하는 Document 를 조회 하였다.

단순히 조회 인자에 /2|3/ 만 넘길수도 있고, $regex 연산자를 이용해 조회를 할 수도 있다.

> db.item.find({itemId : /2|3/})
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002" }
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }

> db.item.find({itemId :
... {
...         $regex : "2|3"
...           , $options : "i"
...         }
... })
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002" }
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }

 

이때, i (대소문자 구분하지 않음) 조건을 추가하면 prefix 조회일 지라도, index 를 사용하지 않는다. 때문에 일부러 소문자로된 값을 반정규화해 관리하거나, 다른방법을 사용해야 성능 에 이슈가 없다.

 

 

범위 조건

범위 조건은 다음과 같이 크다, 작다로 조회 되는 조건들이다.

$lt less then, ~ 보다 작은
$lte less then, equal, ~ 보다 작거나 같은
$gt greater then ~ 보다 큰
$gte greater then, equal ~보다 크거나 같은

위 연산자들은 다음과 같이 쓸수 있다.

샘플 데이터의 itemId 값은 비록 String 타입이지만, gt와 lt 가 정상적으로 적용된다.

> db.item.find({itemId : {$gt : '1000000000002', $lt : '1000000000005' } })
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }
{ "_id" : ObjectId("61642aa3b177ff22016eb520"), "itemId" : "1000000000004" }

하나의 연산자만 쓸수도 있고, 예제처럼 쉼표(,) 를 이용해 여러 조건을 입력할 수 있다.

 한가지 유의할 점이 있다. 일반적으로 하나의 키일 경우, 거의 모든 데이터가 동일한 키라면 동일한 타입으로 입력 되어 있겠지만 오류나 특별한 작업으로 인해 하나의 키에 다른 타입의 데이터가 입력 되었을 수 있다. 이때, 숫자 조건은 숫자로 비교되고, 문자 조건은 문자만 적용된다. 이 점을 유의해야 한다.

 

 

 

집합조건

$in 주어진 값이 하나라도 존재하는 경우
$all 주어진 값 모두 포함되는 경우
$nin 주어진 값이 모두 존재하지 않는 경우

집합조건은 주어진 인수의 값이 연산자에 따라 포함하는지, 포함되지 않는지에 따라 결과를 반환하다.  일반적으로 array 유형을 가지는 키 기준으로 조회 할 경우 유용하게 사용 되지만, 어려개의 조회 조건을 이용하 하나의 값을 가지는 단일형 키 값을 조회 할때도 유용하게 사용된다.

> db.item.find({itemId : {$in : ['1000000000002', '1000000000005']} })
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002" }
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }

> db.item.find({itemId : {$nin : ['1000000000002', '1000000000005']} })
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001" }
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }
{ "_id" : ObjectId("61642aa3b177ff22016eb520"), "itemId" : "1000000000004" }
{ "_id" : ObjectId("61642aa6b177ff22016eb522"), "itemId" : "1000000000006" }
{ "_id" : ObjectId("61642aa8b177ff22016eb523"), "itemId" : "1000000000007" }

> db.item.find({itemId : {$all : ['1000000000002', '1000000000005']} })
>

마지막 all 의 경우, 당연스럽게도 itemId 는 하나의 값만 갖는 데이터니, 두 값을 가질수 없어 하나도 조회가 되지 않았다.

 

BOOL 연산자

$ne not equal, 같지 않은
$not not, 이전 조건을 반대로 만듬. true > false 로 변경
$or 두 조건 중 하나라도 같을 경우 반환
$nor not or, 둘중 하나도 같지 않을 경우 반환
$and 두 조건을 모두 만족해야 반환
$exists 주어진 값이 존재 할경우 true

$ne 의 경우, 조건에 만족하지 않는 값을 반환하고, not일 경우 그 조건을 반전한다.

아래 샘플을 보면 더 쉽게 이해가 갈것이다.

> db.item.find({itemId : {$ne : '1000000000005'} })
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001" }
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002" }
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }
{ "_id" : ObjectId("61642aa3b177ff22016eb520"), "itemId" : "1000000000004" }
{ "_id" : ObjectId("61642aa6b177ff22016eb522"), "itemId" : "1000000000006" }
{ "_id" : ObjectId("61642aa8b177ff22016eb523"), "itemId" : "1000000000007" }

> db.item.find({itemId : {$not : {$ne : '1000000000005'} } })
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }

 

다음은 순서대로 or 와 nor 결과이다. nor 는 or 의 반대 결과를 출력한다.

> db.item.find({
...     $or : [
...         { itemId : '1000000000003'}
...       , { itemId : '1000000000005'}
...     ]
... })
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }


> db.item.find({
...     $nor : [
...         { itemId : '1000000000003'}
...       , { itemId : '1000000000005'}
...     ]
... })
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001" }
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002" }
{ "_id" : ObjectId("61642aa3b177ff22016eb520"), "itemId" : "1000000000004" }
{ "_id" : ObjectId("61642aa6b177ff22016eb522"), "itemId" : "1000000000006" }
{ "_id" : ObjectId("61642aa8b177ff22016eb523"), "itemId" : "1000000000007" }

 

첫번째  $and 는 _id를 일부러 존재 하지 않는 값으로 입력했다. 당연스럽게 두 인자를 만족하는 값이 없으므로 값이 반환되지 않았고, 두번째 쿼리의 경우 정상적으로 반환되었다.

> db.item.find({
...         $and : [
...             { itemId : '1000000000003'}
...           , { _id : 'xxx'}
...         ]
...     })

> db.item.find({
...         $and : [
...             { itemId : '1000000000003'}
...           , { _id : ObjectId("61642aa2b177ff22016eb51f")}
...         ]
...     })
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003" }

 

exists 테스트를 위해 임의로 mdlNm 값을 입력하고, 값이 있는 데이터만 조회 했다.

mdlNm 에 $exists 연산자 입력후 조회 했는데, true, 1 의 결과는 같고, 0일경우에 false 결과로 return 된다.

내부적으로 javascript 와 동일하게 0 이냐 아니냐로 판단값이 결정 된다.

> db.item.update({itemId : '1000000000003'}, { $set : {mdlNm : "prod-1"} });
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
>
> db.item.find({mdlNm : {$exists:true}});
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003", "mdlNm" : "prod-1" }
>
> db.item.find({mdlNm : {$exists:1}});
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003", "mdlNm" : "prod-1" }
>
> db.item.find({mdlNm : {$exists:0}});
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001" }
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002" }
{ "_id" : ObjectId("61642aa3b177ff22016eb520"), "itemId" : "1000000000004" }
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }
{ "_id" : ObjectId("61642aa6b177ff22016eb522"), "itemId" : "1000000000006" }
{ "_id" : ObjectId("61642aa8b177ff22016eb523"), "itemId" : "1000000000007" }

 

 

배열 연산자

이번에는 배열에 대해 조회 해보자. 각각 숫자로된 배열쌍을 입력 후, 하나의 인자만 조회조건으로 넘겼다. 이 경우, 1이 포함된 모든 Document 가 반환된다

> db.item.update({itemId : '1000000000001'}, { $set : {numArray : [1,2,3]} });
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.item.update({itemId : '1000000000002'}, { $set : {numArray : [2,3,4]} });
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.item.update({itemId : '1000000000003'}, { $set : {numArray : [5,6,7]} });
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>
> db.item.find({numArray : 1})
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001", "numArray" : [ 1, 2, 3 ] }

이때, numArray 에 INDEX 가 존재 할 경우, 인덱스를 이용해 데이터를 조회 한다. 이부분은 각각 explain() 을 이용해 확인 해보기 바란다.

 

또 조회 조건으로, 입력된 배열의 순서도 지정해 조회 가능하다. 키 값에 . 을 입력후 숫자를 입력하면 배열 index 값 기준으로 조회된다.

> db.item.find({'numArray.0' : 2})
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002", "numArray" : [ 2, 3, 4 ] }
> db.item.find({'numArray.1' : 2})
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001", "numArray" : [ 1, 2, 3 ] }

 

$elemMatch 주어진 조건이 동일한 하위 Document 에 존재할 경우
$size 주어진 값과 배열 크기가 동일할 경우 

$elemMatch 는 주어진 인자들이 한 도큐먼트내의 서로 다른를지라도 하위 도큐멘트에 하나라도 존재하는 경우 해당 Document 를 반환한다.

db.item.update({itemId : '1000000000001'}, 
  { 
    $set : {option : 
              [ {
                  optionId : 1,
                  optionNm : "option01"
                }
              , {
                  optionId : 2,
                  optionNm : "option02"
                }
              ]
           } 
  }
);

db.item.find(
  {
    option : {
      $elemMatch : {
          optionId : 1
        , optionNm : "option02"
        }
    }
  }  
)

 

 

$size 는 지정된 배열의 크기가 주어진 값과 같을 경우, 해당 Docuemnt 를 반환한다.

> db.item.find({numArray: {$size:3}})
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001", "numArray" : [ 1, 2, 3 ], "option" : [ { "optionId" : 1, "optionNm" : "option01" }, { "optionId" : 2, "optionNm" : "option02" } ] }
{ "_id" : ObjectId("61642a9fb177ff22016eb51e"), "itemId" : "1000000000002", "numArray" : [ 2, 3, 4 ] }
{ "_id" : ObjectId("61642aa2b177ff22016eb51f"), "itemId" : "1000000000003", "mdlNm" : "prod-1", "numArray" : [ 5, 6, 7 ] }

 

$size 일 경우, $gt, $lt 연산자와 같이 사용이 불가능하다. 그렇기때문에 배열 크기를 기준으로 조회 해야 되는 케이스가 존재 하면, 배열 크기를 기준으로 조회 하는 것이 아닌, 배열 크기를 새로인 키로 반정규화 해서 따로 관리 해야 한다.

예를 들어 다음과 같이 말이다.

{
        "_id" : ObjectId("61642a9fb177ff22016eb51e"),
        "itemId" : "1000000000002",
        "numArray" : [2,3,4],
        "numArrayCnt" : 3
}

 

배열로된 키의 값이 많을 경우, 해당 값도 잘라 볼수 있다. 바로 $slice 연산자인데 사용법은 다음과 같다.

> db.item.update({itemId : '1000000000001'}, { $set : {numArray : [1,2,3,4,5,6,7,8,9]} });
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

> db.item.find({itemId : '1000000000001'}, {numArray : {$slice: 3}})
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001", "numArray" : [ 1, 2, 3 ] }

> db.item.find({itemId : '1000000000001'}, {numArray : {$slice: -3}})
{ "_id" : ObjectId("61642a98b177ff22016eb51d"), "itemId" : "1000000000001", "numArray" : [ 7, 8, 9 ] }

$slice 의 조건을 양수로 주면 앞에서부터, 음수로 주면 뒤에서부터 해당 값 만큼 출력된다.

 

 

 

JAVA 스크립트 연산자

위에서 소개한 연산자로 조회 할 수 없는 케이스면, 직접 조회 조건을 function 으로 구현 할 수 있다.

> db.item.find(   {$where :    "function(){return this.itemId == '1000000000005';}"   } );
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }

function 으로 조회 조건을 만들어 조회 하는 방식이다. function 을 빼고도 가능하다. 조회 결과는 동일 하다.

> db.item.find(
...   {$where :
...   "this.itemId == '1000000000005'"
...   }
... );
{ "_id" : ObjectId("61642aa5b177ff22016eb521"), "itemId" : "1000000000005" }

 

 역시, custom 으로 연산자를 만든 자바스크립트연산자는 index를 사용 할 수 없다. 때문에, 인덱스를 사용하는 컬럼 기준으로 먼저 데이터를 조회하고, 일부 필터 조건으로 사용하는 등 사용 범위를 제한 하여야 한다.

 

 

$text - 텍스트 연산자

 텍스트 연산자는 텍스트 인덱스로 인덱싱된 필드 내용을 검색할때 사용된다. 

 

 

반응형