Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

Mongodb Find 예제

MongoDB를 이용 할 때 가장 짜증나는게 질의어 만들기다. 익숙함의 문제라고 하는 개발자도 있지만, SQL과 비교해서 보자면 근본적인 문제점을 가지고 있다. 요즘은 언어세대라는 용어를 별로 쓰지 않는 것 같은데, 이 분류를 따르자면 SQL은 4세대 언어다. 즉 인간의 언어와 매우 비슷한 문법을 가지고 있다는 이야기다. 아래 예제를 보자.
SELECT person, SUM(score), AVG(score), MIN(score), MAX(score), COUNT(*) 
FROM demo 
WHERE score > 0 AND person IN('bob','jake') 
GROUP BY person;
SQL 언어를 처음 보는 사람이라고 하더라도 간단한 설명만으로 어떤일을 하는 질의어라는 걸 한번에 이해 할 수 있을 것이다. 몇 번만 연습하면 응용을 할 수 있을 테고, 메뉴얼을 읽지 않더라도 왠만한 질의어는 만들어 사용 할 수 있을 것이다. 몽고디비는 자바스크립트 문법을 사용하는데, SQL에 비하면 상상도 할 수 없을 만큼 어렵다. 위의 SQL을 몽고디비 쿼리로 변경했다.
db.demo.group({
    "key": {
        "person": true
    },
    "initial": {
        "sumscore": 0,
        "sumforaverageaveragescore": 0,
        "countforaverageaveragescore": 0,
        "countstar": 0
    },
    "reduce": function(obj, prev) {
        prev.sumscore = prev.sumscore + obj.score - 0;
        prev.sumforaverageaveragescore += obj.score;
        prev.countforaverageaveragescore++;
        prev.minimumvaluescore = isNaN(prev.minimumvaluescore) ? obj.score : Math.min(prev.minimumvaluescore, obj.score);
        prev.maximumvaluescore = isNaN(prev.maximumvaluescore) ? obj.score : Math.max(prev.maximumvaluescore, obj.score);
        if (true != null) if (true instanceof Array) prev.countstar += true.length;
        else prev.countstar++;
    },
    "finalize": function(prev) {
        prev.averagescore = prev.sumforaverageaveragescore / prev.countforaverageaveragescore;
        delete prev.sumforaverageaveragescore;
        delete prev.countforaverageaveragescore;
    },
    "cond": {
        "score": {
            "$gt": 0
        },
        "person": {
            "$in": ["bob", "jake"]
        }
    }
});
이러한 질의어를 만들 수 있을까 ? 물론 몽고디비는 SQL과는 목적이 다르므로, 질의과정에서 연산을 수행하는 쿼리를 만드는 것은 불공정한 예제라고 할 수 있겠다. 요는 "SQL에 비하면 엄청나게 복잡하다"는 거다. http://www.querymongo.com/와 같은 사이트를 이용하거나 다른 툴들이 필요 할 수 있다. 애초에 뭔가 연산이 복잡해 질 것 같으면 SQL을 이용하는 데이터베이스를 사용하는게 좋다.

테스트 데이터 준비

몽고디비 shell을 기준으로 테스트했다. 테스트에 사용할 데이터는 http://media.mongodb.org/zips.json이다. wget으로 다운로드 한 다음 임포트 했다.
$ wget http://media.mongodb.org/zips.json 
$ mongoimport --db test --collection zips --drop --file zips.json 
2017-07-23T00:17:47.276+0900	connected to: localhost
2017-07-23T00:17:47.276+0900	dropping: test.zips
2017-07-23T00:17:47.508+0900	imported 29353 documents

find 기본

find() 메서드를 이용한다.
> db.zips.find()
{ "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA" }
{ "_id" : "01002", "city" : "CUSHMAN", "loc" : [ -72.51565, 42.377017 ], "pop" : 36963, "state" : "MA" }
{ "_id" : "01005", "city" : "BARRE", "loc" : [ -72.108354, 42.409698 ], "pop" : 4546, "state" : "MA" }
{ "_id" : "01007", "city" : "BELCHERTOWN", "loc" : [ -72.410953, 42.275103 ], "pop" : 10579, "state" : "MA" }
{ "_id" : "01008", "city" : "BLANDFORD", "loc" : [ -72.936114, 42.182949 ], "pop" : 1240, "state" : "MA" }
{ "_id" : "01010", "city" : "BRIMFIELD", "loc" : [ -72.188455, 42.116543 ], "pop" : 3706, "state" : "MA" }
{ "_id" : "01011", "city" : "CHESTER", "loc" : [ -72.988761, 42.279421 ], "pop" : 1688, "state" : "MA" }
.....
Type "it" for more
>
it를 입력해서 다음 셋을 가져올 수 있다.

특정 필드만 반환하기

옵션을 설정하지 않을 경우, 모든 필드를 출력한다. 아래와 같이 출력할 필드를 특정 할 수 있다.
> db.zips.find({},{city:1})
{ "_id" : "01001", "city" : "AGAWAM" }
{ "_id" : "01002", "city" : "CUSHMAN" }
{ "_id" : "01005", "city" : "BARRE" }
{ "_id" : "01007", "city" : "BELCHERTOWN" }
....
2개 이상의 필드를 설정 할 수 있다.
> db.zips.find({},{city:1, pop:1})
{ "_id" : "01001", "city" : "AGAWAM", "pop" : 15338 }
{ "_id" : "01002", "city" : "CUSHMAN", "pop" : 36963 }
{ "_id" : "01005", "city" : "BARRE", "pop" : 4546 }

Exact matching

find를 이용해서 일치하는 정보를 찾을 수 있다.
문법 : {field: "찾기 원하는 값"}
NEW YORK시(city)의 zip코드 정보만 읽어보자.
> db.zips.find({city:"NEW YORK"})
{ "_id" : "10001", "city" : "NEW YORK", "loc" : [ -73.996705, 40.74838 ], "pop" : 18913, "state" : "NY" }
{ "_id" : "10002", "city" : "NEW YORK", "loc" : [ -73.987681, 40.715231 ], "pop" : 84143, "state" : "NY" }
{ "_id" : "10003", "city" : "NEW YORK", "loc" : [ -73.989223, 40.731253 ], "pop" : 51224, "state" : "NY" }
....

비교 일치

$lt는 value 보다 작은 값(<)을 찾기 위해서 사용한다. $gt는 value 보다 큰 값(>)을 찾기 위해서 사용한다.
문법 : {field: {$lt: value}}
pop필드의 값이 100보다 크고, 200보다 작은 zip 정보를 읽어보자.
> db.zips.find({pop:{$gt:100, $lt:200}})
{ "_id" : "01012", "city" : "CHESTERFIELD", "loc" : [ -72.833309, 42.38167 ], "pop" : 177, "state" : "MA" }
{ "_id" : "01032", "city" : "GOSHEN", "loc" : [ -72.844092, 42.466234 ], "pop" : 122, "state" : "MA" }
{ "_id" : "01258", "city" : "SOUTH EGREMONT", "loc" : [ -73.456575, 42.101153 ], "pop" : 135, "state" : "MA" }
{ "_id" : "01346", "city" : "HEATH", "loc" : [ -72.839101, 42.685347 ], "pop" : 174, "state" : "MA" }
{ "_id" : "02836", "city" : "RICHMOND", "loc" : [ -71.683992, 41.477694 ], "pop" : 183, "state" : "RI" }
{ "_id" : "02872", "city" : "PRUDENCE ISLAND", "loc" : [ -71.311827, 41.613606 ], "pop" : 150, "state" : "RI" }
{ "_id" : "03231", "city" : "EAST ANDOVER", "loc" : [ -71.759606, 43.47766 ], "pop" : 177, "state" : "NH" }
{ "_id" : "03268", "city" : "SALISBURY", "loc" : [ -71.704468, 43.406652 ], "pop" : 140, "state" : "NH" }

배열에서의 Find

배열의 각 필드에 대해서 find 할 수도 있다. loc[0]의 -72.1에서 -72.2 사이에 있는 값들을 찾아보자.
> db.zips.find({'loc.0':{$gt:-72.2, $lt:-72.1}},{city:1, loc:1})
{ "_id" : "01005", "city" : "BARRE", "loc" : [ -72.108354, 42.409698 ] }
{ "_id" : "01010", "city" : "BRIMFIELD", "loc" : [ -72.188455, 42.116543 ] }
{ "_id" : "01031", "city" : "GILBERTVILLE", "loc" : [ -72.198585, 42.332194 ] }
{ "_id" : "01366", "city" : "PETERSHAM", "loc" : [ -72.189349, 42.489761 ] }
{ "_id" : "01518", "city" : "FISKDALE", "loc" : [ -72.117764, 42.122762 ] }
{ "_id" : "01521", "city" : "HOLLAND", "loc" : [ -72.154373, 42.040264 ] }

정렬

sort() 메서드를 이용해서 정렬 할 수 있다. NEW YORK의 결과를 pop 필드를 기준으로 정렬해보자.
> db.zips.find({city:"NEW YORK"}).sort({pop:1})
{ "_id" : "10006", "city" : "NEW YORK", "loc" : [ -74.013474, 40.708451 ], "pop" : 119, "state" : "NY" }
{ "_id" : "10005", "city" : "NEW YORK", "loc" : [ -74.008344, 40.705649 ], "pop" : 202, "state" : "NY" }
{ "_id" : "10020", "city" : "NEW YORK", "loc" : [ -73.982347, 40.759729 ], "pop" : 393, "state" : "NY" }
{ "_id" : "10007", "city" : "NEW YORK", "loc" : [ -74.007022, 40.713905 ], "pop" : 3374, "state" : "NY" }
......
1이면 오름차순, -1이면 내림차순이다.
> db.zips.find({city:"NEW YORK"}).sort({pop:-1})
{ "_id" : "10021", "city" : "NEW YORK", "loc" : [ -73.958805, 40.768476 ], "pop" : 106564, "state" : "NY" }
{ "_id" : "10025", "city" : "NEW YORK", "loc" : [ -73.968312, 40.797466 ], "pop" : 100027, "state" : "NY" }
{ "_id" : "10002", "city" : "NEW YORK", "loc" : [ -73.987681, 40.715231 ], "pop" : 84143, "state" : "NY" }
{ "_id" : "10029", "city" : "NEW YORK", "loc" : [ -73.94475, 40.791817 ], "pop" : 74643, "state" : "NY" }
{ "_id" : "10024", "city" : "NEW YORK", "loc" : [ -73.976385, 40.786446 ], "pop" : 65141, "state" : "NY" }
{ "_id" : "10032", "city" : "NEW YORK", "loc" : [ -73.941978, 40.83819 ], "pop" : 61332, "state" : "NY" }
......

limit 와 skip

limit() 메서드를 이용해서 데이터 갯수를 제한 할 수 있다.
> db.zips.find({city:"NEW YORK"},{city:1, pop:1}).sort({pop:-1}).limit(5)
{ "_id" : "10021", "city" : "NEW YORK", "pop" : 106564 }
{ "_id" : "10025", "city" : "NEW YORK", "pop" : 100027 }
{ "_id" : "10002", "city" : "NEW YORK", "pop" : 84143 }
{ "_id" : "10029", "city" : "NEW YORK", "pop" : 74643 }
{ "_id" : "10024", "city" : "NEW YORK", "pop" : 65141 }
skip() 메서드를 이용해서 출력데이터의 시작부분을 설정할 수 있다.
> db.zips.find({city:"NEW YORK"},{city:1, pop:1}).sort({pop:-1}).limit(5).skip(3)
{ "_id" : "10029", "city" : "NEW YORK", "pop" : 74643 }
{ "_id" : "10024", "city" : "NEW YORK", "pop" : 65141 }
{ "_id" : "10032", "city" : "NEW YORK", "pop" : 61332 }
{ "_id" : "10033", "city" : "NEW YORK", "pop" : 58648 }
{ "_id" : "10009", "city" : "NEW YORK", "pop" : 57426 }
Mysql의 limit, offset와 같은 일을 한다. 이들 메서드를 이용해서 페이징을 할 수 있다.