2011. 9. 19. 18:56

[mongodb] Querying

* find 소개

db.a.find()
; collection a에 있는 모든 document 검색

db.a.find({"k":"1", "b":2})
; collection a에서 k=1, b=2인 모든 document 검색
(condition AND condition AND ...)

db.a.find({}, {"a":1, "b":1})
; a, b key만 리턴한다. (_id key는 항상 리턴된다)

db.a.find({}, {"a":1, "_id":0})
; a key만 리턴된다. _id key는 제외된다.

* 제약 사항

; find에 사용되는 query value 값은 상수여야 한다.

db.a.find({"a":1})
{"a":1}

상황에서,

var node={"query":1}
var def=1

db.a.find({"a":node.query})
{"a":1}
db.a.find({"a":this.def})
{"a":1}
와 같이 query는 잘 되었다.
여하튼, query value 값은 상수여야 하는 제약 조건을 잘 지켜야 한다.

그리고,
var test={"keyname":"a"}
db.a.find({"keyname.a":1})
는 당연히 query되지 않고,
db.a.find({keyname.a:1})
하면 오류가 발생한다.

즉, key 이름도 당연히 상수여야 할 듯하다.

* query 조건들

; comparision operator
$lt, $lte, $gt, $gte --> <, <=, >, >=

db.a.find({"num":{"$gte":2, "$lte":5}})

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

db.a.find({"name":{"$ne":"joe"}})

; OR query
$in - 단일 key에 대한 여러가지 값 (<-> $nin)
$or - 보다 일반적
db.a.find({"answer":{"$in":[1,2,4]}})
db.a.find({"answer":{"$nin":[3,5]}})
db.a.find({"$or":[{"attr":1}, {"attr2":2}]})

; $not
db.a.insert({"a":1})
...
db.a.insert({"a":13})
db.a.find({"a":{"$mod":[5,1]}})
{"a":1}
{"a":6}
{"a":11}
$mod는 나머지 연산자
db.a.find({"a":{"$not":{$mod":[5,1]}}})
{"a":2}
... => "a"는 2,3,4,5,6,7,8,9,10,12,13 이 리턴

; conditions 규칙
$-prefixed key는 각각 적절한 다른 위치를 가진다.
; inner(안쪽) document - $lt, ...
; outer(바깥쪽) document - $inc, ...
db.a.find({"a":{"$lt":3, "$gt":2}})
{"$inc":{"age":1}, "$set":{age:40}}

* type 지정 query (type-specific queries)

; null
db.a.find({"a":null})
; 그 자체로 match되고, 또한 "does not exist"(=> $exists)도 match 한다.

만약, "a":null 문서가 있다면 -> 그것을 리턴
만약, "a" key가 문서에 없다면 -> 모두 리턴
db.a.find({"a":{"$in":[null], "$exists":true}})

; regular expression
db.a.find({"name":/joe/i})

; array query하기
db.a.insert({"a":[1,2,3]})
db.a.find({"a":2})
{a:[1,2,3]}
$all
db.a.insert({"a":1, "b":[1,2,3]})
db.a.insert({"a":2, "b":[2,3,4]})
db.a.insert({"a":3, "b":[3,4,5]})
db.a.find({"b":{$all:[2,3]})
{a:1, b:[1,2,3]}
{a:2, b:[2,3,4]}
db.a.find({"b.2", 3})
{a:1, b:[1,2,3]}
; 0-based index로 find 가능

$size
array의 크기 비교
$gt등과 조합 못함
db.a.find({"b":{"$size":3}})

$slice
array key의 subset 리턴.

; embeded document query 하기
; document 전체를 query하는 방법과 개별 key/value pair로 query하는 방법이 있다.
db.a.insert({"a":{"q":1, "w":2}})
db.a.find({"a":{"q":1, "w":2}})
{"a":{"q":1, "w":2}}
db.a.find({"a":{"q":1}})

와 같이 일반 key/value로 하면 query 결과가 나오지 않는다.
이를 방지하려면 다음과 같이 하라.
db.a.find({"a.q":1}})
{"a":{"q":1, "w":2}}
다음과 같은 document가 있다고 하자.

 {
  "arr" : [
   {
    "a" : 1,
    "b" : 1,
    "c" : 1
   },
   {
    "a" : 2,
    "b" : 2,
    "c" : 2
   }
  ]
 }

db.a.find({"arr":{"a":1, "b":1}) 하면 결과가 없다. c 조건이 없기 때문이다.
db.a.find({"arr":{"a":1, "b":{"$gte":1}}}) 하면 결과가 없다.

db.a.find({"arr.a":1, "arr.b":2}) 하면 query된다. a=1,b=2인 내용이 없지만 query된다.
db.a.find({"arr.a":1, "arr.b":3}) 하면 결과가 없다.
==> 즉, .로 사용된 key가 2개 이상인 경우, 내부 element 중 모두 충족하면 query된다.
만일, array 내부의 document에 a=1, b=2를 지원하는 document가 있는지 query해야 한다면?
db.a.find({"arr":{"$elemMatch":{"a":1,"b":2}}})
하면 내용이 없다.
위 예에서 a:1,b:2,c:3을 추가하면 query된다.

* $where query

key/value pair로 query하는것은 표현력이 좋으나, 표현하기 힘든 query도 존재한다.
JavaScript와 함께 사용하여 query를 좀더 유연하게 하는것이 "$where"이다.
db.a.insert({"a":1, "b":2, "c":3})
db.a.insert({"a":11,"q":9, "w":9})
db.a.find({"$where":"this.b+this.c==5"})
{"a":1, "b":2, "c":3}
db.a.find({"$where":function(){return this.b+this.c==5}})
{"a":1, "b":2, "c":3}
보통의 query 보다 느리다.
각각의 document는 BSON -> JavaScript Object로 변환된다.
복잡한 query를 할 수 있는 다른 방법중 하나는 MapReduce이다.

* cursor
 
for (i=0; i<100; i++) { db.a.insert({x:i}); }
var cursor = db.a.find();

; limit, skip, sort
db.c.find().limit(3) - 3개의 결과만.
db.c.find().skip(3) - 3개 다음으로.
db.c.find().sort({a:1}) - a key로 1=ascending sort
db.c.find().sort({a:-1}) - a key로 -1=decending sort

; skip의 양 줄이기
skip에는 작은 값이 좋다. 만약 result가 많다면, skip은 느리며, 이는 기피 대상이다.

; skip 없이 page 나누기
var page1 = db.a.find(...).limit(100)
var page2 = db.a.find(...).skip(100).limit(100)
var page3 = db.a.find(...).skip(200).limit(100)

이런경우, query만 사용하여 skip없이 만들수 있다.

var page1 = db.a.find().sort({"a":-1}).limit(100)
var latest = null;

// display first page
while (page1.hasNext()) {
   latest = page1.next();
   display(latest);
}
// get next page
var page2 = db.a.find({"a":{"$gt":latest.date}});
page2.sort({"a":-1}).limit(100);
==> skip 없이 구현되었다.

; random 문서 구하기
// skip 사용 (사용하지 말 것)
var toal = db.a.count()
var random = Math.floor(Math.random()*total)
db.a.find().skip(random).limit(1)
// skip 없이 사용
db.a.insert({"a":1, "random":Math.random()})
db.a.insert({"a":2, "random":Math.random()})
db.a.insert({"a":3, "random":Math.random()})
var random = Math.random()
result = db.a.findOne({"random":{"$gt":random}})
if (null == result)
{
  result = db.a.findOne({"random":{"$lt":random}})
}

혹은 ensureIndex를 이용하기도 한다.

; 일관된 결과 만들기
cursor = db.foo.find();

while (cursor.hasNext()) {
  var doc = cursor.next();
  doc = process(doc);
  do.foo.save(doc);

즉, 모든 document를 process하고, save한다.
그런데, 만일,
1 -> 2 -> 3 -> ... 과 같은 순서의 document가 진행될 때,
2번 document를 process하여 저장하는데,
만일 db의 크기가 커지면 relocate가 발생할 수 있다.
그러면,
1 -> () -> 3 -> ... -> 2
와 같이 되는데, 그러면 위 while loop에서 문제가 생길 수 있다.
이를 해결하기 위해서는 "$snapshot" option이 필요하다.