Database/MongoDB 2019. 9. 13. 18:55

 

이번 포스팅은 간단하게 MongoDB 사용법에 대해 다루어봅니다. 모든 쿼리는 특정 클라이언트 드라이버를 이용하는 것이 아니라, Shell을 이용하여 직접 쿼리를 작성해보는 내용입니다. 실습 이전에 혹시나 몽고디비 설치가 되어 있지 않다면 설치가 필요합니다. 필자는 Docker를 이용하여 몽고디비를 설치하였으며 방법은 아래 링크를 참조하시면 됩니다. 또한 모든 예제는 몽고디비 공식 도큐먼트를 기준으로 작성하였습니다. (번역에 익숙치 않아 가끔 잘못된 번역이 있을 수도 있습니다.)

 

 

Docker - Docker로 MongoDB 설치하기.

이번 포스팅은 Docker를 이용하여 MongoDB를 설치하는 방법이다. 도커 실행 후 MongoDB 이미지를 받아준다. 이미지가 잘 받아진 것을 확인한다. 이제 내려받은 이미지를 실행시키자. 각 옵션은 이전 도커를 이..

coding-start.tistory.com

 

모든 실습환경은 MacOS 환경에서 작성되었습니다. 또한 몽고디비 버전은 v4.2.0 기준으로 작성하였습니다.

 

> docker exec -it mongodb bash

 

터미널에서 위의 명령으로 몽고디비 컨테이너로 접속합니다.

 

위의 이미지처럼 "mongo"라는 명령어를 입력하면 쉘을 이용해 몽고디비 서버에 접속하게 됩니다. 여기까지 실습을 위한 준비입니다.

 

 

몽고디비는 위와 같이 BSON구조의 도큐먼트로 데이터를 다루며, 해당 도큐먼트의 모음으로 Collection이라는 단어를 사용합니다. 그리고 이러한 Collection의 모음은 하나의 database라는 큰 공간에 존재하게 됩니다. 그럼 당연히 database를 생성하는 게 첫번째입니다.

 

Mongo RDBMS
database database
collection table
document row 혹은 record
field column

 

<User 생성>

기본적으로 몽고디비가 설치가 되면 별도의 인증없이 접속이 가능합니다. 그렇기 때문에 유저를 생성하여 해당 데이터베이스에 접속할때 간단한 아이디/패스워드로 인증설정을 해주어야합니다.

 

 

첫번째는 admin database에 유저를 생성해주는 것이고 두번째는 우리가 예제로 사용할 test_database의 유저를 생성해주는 예제입니다. 하지만 우리는 도커를 이용해 컨테이너를 실행시킬때 빼먹은 것이 있습니다. docker run 명령어 입력시 마지막에 --auth를 붙여주어야 원격에서 몽고디비 서버에 붙을 때 인증을 요구합니다.

 

그리고 인증된 유저로 데이터베이스를 이용하기 위해서는 여러가지 방법이 있지만 여기서는 "mongo"로 서버에 접속한 후 사용하기 위한 데이터베이스로 스위치합니다.

 

>use test_database

 

그리고 해당 데이터베이스를 활용하기 위해 인증을 해줍니다.

 

>db.auth("testuser","testuser") ->id/pwd

 

 

<Database 생성>

사실 몽고디비에는 database라는 생성 명령어가 존재하지 않습니다. database 생성 시점은 해당 database를 사용하고 첫번째 데이터가 삽입 혹은 인덱스가 생성된 시점에 자동으로 생성됩니다.

 

> use test_database

 

위 명령어를 입력하여 서버에게 데이터베이스 사용을 알려줍니다.

 

 

결과값입니다. 이것은 마치 Git에서 특정 branch로 checkout하는 것과 비슷한 결과값을 보여줍니다.

 

> db.testcollection.insertOne({name:"yeoseong",age:28,address:{si:"seoul",gu:"gangbuk"}})

 

위의 명령어로 데이터를 하나 삽입해줍니다.

 

 

위 이미지와 같은 결과값이 나왔다면 삽입성공입니다. 디폴트로 생성된 문서 아이디를 반환해줍니다. 그리고 해당 데이터베이스의 컬렉션으로 처음 데이터가 삽입된 시점에 database와 collection이 생성됩니다. 물론 collection 같은 경우는 다양한 옵션 조작을 위해 명시적으로 생성가능합니다.

 

여기서 하나의 명령어를 더 다루어봅니다.

 

> db.getCollectionInfos()

 

위 명령어는 해당 데이터베이스의 모든 컬렉션의 정보를 보여줍니다.

 

 

컬렉션의 UUID는 샤드 클러스터의 모든 Replica-Set 및 샤드에 동일하게 유지되는 불변값입니다. 아마 클러스터의 모든 컬렉션을 유일하게 식별하기 위한 아이디값이 아닐까 싶습니다.

 

<Index 생성>

RDBMS에 인덱스가 존재하듯 몽고디비에도 인덱스라는 개념이 존재합니다. 하지만 이러한 인덱스는 스키마 구조를 처음에 작성해주어야 하기 때문에 몽고디비가 100% schema-less한 NoSQL이 아니라고 합니다.

 

> db.testcollection.createIndex({name:1})

 

위의 명령으로 인덱스를 생성해줍니다. 인자로 인덱스로 사용할 필드 이름과 숫자가 들어갑니다. 여기서 숫자는 1,-1 값을 입력할 수 있습니다. 이 숫자가 의미하는 바는 인덱스의 내부 자료구조를 살펴봐야합니다. 기본적으로 몽고디비는 B-Tree라는 자료구조로 인덱스를 생성합니다. B-Tree는 내부적으로 정렬된 형태의 자료구조이므로 이 말은 인덱스의 데이터 값도 정렬되서 들어간다는 뜻입니다. 즉, 1은 오름차순, -1은 내림차순으로 인덱스 값을 정렬하는 설정입니다. 

 

그리고 인덱스는 하나가 아니고 여러개의 필드를 지정할 수 있습니다. 사실 이렇게 인덱스를 필요로하는 컬렉션은 명시적으로 스키마 정보를 입력해줘야 하지만 인덱스 이외의 문서의 필드는 schema-less한 특성을 갖습니다.

 

마지막으로 위에서 몽고디비는 인덱스 자료구조로 B-Tree를 이용한다 했는데, 사실 B-Tree만 사용하는 것이 아닙니다. 문서 특성에 따라 다양한 인덱스 자료구조를 갖습니다. 이 내용은 추후에 다루어보도록 하겠습니다.

 

<MongoDB CRUD>

 

1)Insert삽입

 

-단일 문서삽입

 

> db.testcollection.insertOne({name:"yeoseong",age:28,address:{si:"seoul",gu:"gangbuk"}})

 

만약 _id 필드를 명시하지 않으면 랜덤한 ObjectId를 자동으로 생성해줍니다. 필드 데이터 타입은 추후에 제대로 다루어볼 것이지만, 날짜,배열,오브젝트,문자열,숫자 등 다양한 타입을 지원합니다. 그나마 우리가 사용하는 JSON 구조이기에 이해하기가 편합니다.

 

위의 단일 삽입 명령어를 작성하면 할당된 ObjectId가 결과값으로 반환됩니다.

 

-벌크 문서삽입(다수 문서를 한번에 삽입)

 

> db.testcollection.insertMany([{name:"sora"},{name:"yoon"}])

 

위의 명령을 수행하면 2개의 문서를 한번에 삽입합니다.

 

여기서 하나 의문점이 드는 것이 트랜잭션인데, 과연 2개의 문서를 삽입하는 명령 하나가 하나의 트랜잭션으로 묶일까? 답은 아니다 입니다. 내부적으로는 2개의 문서를 삽입하는 명령을 각각 하나의 삽입문으로 스플릿해서 각각이 하나의 트랜잭션을 갖게 됩니다. 이 말은 첫번째 sora가 삽입성공하고 yoon삽입이 실패하면 서로 각각 다른 트랜잭션을 갖기때문에 sora는 삽입된 상태에서 롤백되지 않습니다. 왜냐하면 몽고디비는 단일 문서 수준의 트랜잭션을 제공하기 때문입니다.

(3.4버전 기준 트랜잭션 내용인데.. 현재 4.x는 어떻게 되는지 모르겠내요.. 혹시 아시는 분은 댓글 부탁드립니다.)

 

_id Field

몽고디비에서 각각의 도큐먼트는 기본키 역할을 하는 _id 필드를 꼭 가져야한다. 문서 삽입 오퍼레이션에서 _id 필드를 생략한다면, 몽고디비는 ObjectId를 자동으로 생성하여 _id 필드에서 넣어준 후에 결과값으로 해당 ObjectId를 반환한다.

 

Atomicity

몽고디비는 단일 문서 기준으로 원자성을 제공한다. 위에서도 얘기했던 다수 문서를 한번에 삽입하는 벌크 문장에서 하나의 명령으로 여러개의 문서를 삽입한다고 해도, 하나의 트랜잭션으로 묶이는 것이 아닌 각각의 오퍼레이션에 대한 트랜잭션이 각각 실행된다.

 

Insert Methods

몽고디비는 컬렉션에 문서를 삽입하기위한 메서드를 아래와 같이 제공한다.

 

Method 설명
db.collection.insertOne() 컬렉션에 단일 문서 삽입.
db.collection.insertMany() 컬렉션에 다수 문서 삽입.
db.collection.insert() 컬렉션에 단일 혹은 다수 문서 삽입.

 

또한 추가적으로 삽입에 사용되는 메서드이다.

 

Method 설명
db.collection.update() "upsert: true" 옵션 사용시
db.collection.updateOne() "upsert: true" 옵션 사용시
db.collection.updateMany() "upsert: true" 옵션 사용시
db.collection.findAndModify() "upsert: true" 옵션 사용시
db.collection.findOneAndUpdate() "upsert: true" 옵션 사용시
db.collection.findOneAndReplate() "upsert: true" 옵션 사용시
db.collection.save()  
db.collection.bulkWrite()  

 

위 메서드들은 따로 update 쿼리 부분에서 다루어볼 것이다.

 

 

2)Query Document

 

-Select All Document in a Collection

매개변수를 생략하고 단순히 조회 메서드를 이용하면 컬렉션 안에 있는 모든 문서를 조회할 수 있다. 매개변수로 들어가는 쿼리 필터 파라미터는 조회 기준을 결정한다.

 

> db.collection.find({}) / db.collection.find()

 

위의 메서드는 마치 SQL에서 "SELECT * FROM inventory" 와 같은 오퍼레이션과 같다.

 

-Specify Equality Condition

동치 조건을 지정하기 위해서, 조회 메서드 매개변수로 <field>:<value> 형식의 쿼리 필터 파라미터를 전달한다.

 

> db.collection.find( { <field>:<value> } )

 

> db.testcollection.find( { name:"yeoseong" } )

 

위 오퍼레이션에 대한 결과값이다.

 

 

위 메서드는 SQL에서 SELECT * FROM testcollection WHERE name = "yeoseong"과 같은 오퍼레이션과 같다.

 

-Specify Conditions Using Query Operators

조회 메서드에 쿼리 오퍼레이터를 이용하여 어떠한 조건을 지정할 수 있다.

 

> db.testcollection.find( { age:{ $in:[27,28,29] } } )

 

age가 27,28,29 인 문서를 조회한다. (OR) $or 조건을 사용할 수 있지만 같은 필드로 동치를 수행할 때는 $in을 사용하라고 몽고디비 문서에서는 권장함.

 

-Specify AND Conditions

복합 쿼리는 컬렉션의 문서를 조회하기 위해 하나 이상의 필드에 대해 다수의 조건을 지정할 수 있다. ","로 구분하면 암시적으로 AND 연산자가 사용되어 일치하는 문서를 찾는다.

 

>db.testcollection.find( { name:"yeoseong",age: { $lt:30 } })

 

위 쿼리절은 나이가 30살보다 어리면서 이름이 yeoseong인 문서를 찾는다. 이것은 SQL에서 SELECT * FROM testcollection WHERE name = "yeoseong AND age < 30 과 같다.

 

-Specify OR Conditions

$or 오퍼레이션을 이용해서 하나 이상의 조건과 일치하는 문서를 찾는 조회쿼리를 작성할 수 있다.

 

>db.testcollection.find( {$or:[{name:"yeoseong"},{name:"sora"}]} )

 

위 쿼리절은 이름이 yeoseong 이거나 sora인 문서를 찾는다. SQL에서는 SELECT * FROM testcollection WHERE name = "yeoseong OR name = "sora"와 같다.

 

-Specify AND as well as OR Conditions

AND와 OR 조건을 같이 사용하여 복합 쿼리 작성도 가능하다.

 

>db.testcollection.find({age:{$lt:30},$or:[{name:/^y/},{name:/^s/}]})

 

위 쿼리절은 나이가 30살보다 어리고 이름이 y로 시작하거나 s로 시작하는 문서를 찾는다. SQL에서는 SELECT * FROM testcollection WHERE age < 30 AND ( name LIKE "y%" OR name LIKE "s%" ) 와 같다. 여기서 사용되는 /^y/ 같은 연산자는 문자열 타입으로 지정된 인덱스 필드에 쿼리 성능 향상에 도움이 되는 연산자이다. 

 

 

DB - MongoDB 조건으로 일부 문자열만 사용하여 인덱스이용하기

이번 포스팅할 내용은 짧은 내용이다. 아래와 같은 Document가 있다고 생각해보자. {_id:"1234",name:"yeoseong",age:"28",address:"aaa"} 위의 도큐먼트에서 인덱스된 필드가 "name"필드라고 가정하자. 위의 도..

coding-start.tistory.com

 

> db.testcollection.findOne() 

 

위의 명령어는 단일 문서를 읽어오는 오퍼레이션이다.

 

-Query on Embedded/Nested Document

쿼리 필터 파라미터를 이용하여 embedded/nested한 필드에 대한 조건을 지정할 수 있다. 이 말은 하나의 필드안에 또 다른 문서가 들어간 중첩 필드에 대한 쿼리 조건절을 지정할 수 있다는 뜻이다.

 

db.testcollection.find({"address.si":"seoul"})

 

위 쿼리절은 address 필드 안에 있는 문서 중 si 필드가 seoul인 문서를 조회한다. 이 쿼리에 대한 결과이다.

 

 

여기서 하나 주의해야 할것이 있다. 아래와 같은 쿼리절 두개가 있다고 생각하자.

 

> db.testcollection.find({address:{si:"seoul",gu:"gangbuk"}}) 결과o

> db.testcollection.find({address:{gu:"gangbuk",si:"seoul"}}) 결과x

 

우리는 대게 위의 결과가 같다고 생각할 것이다. 하지만 하나는 결과값이 나오고, 하나는 결과값이 나오지 않는다. 그 이유는 임베디드된 문서 필드 전체에 대해 조건절을 수행할 경우 필드의 순서를 같게 넣어줘야 한다. 위의 결과 이미지를 보면 address 임베디드는 si,gu 순서로 필드가 되어 있으므로, 조회 조건에도 동일한 순서로 넣어줘야한다.

 

-Query on Nested Field

위는 임베디드되거나 중첩된 문서에 대한 매칭을 수행한 예제였다면, 이번은 임베디드되거나 중첩된 문서의 특정 필드를 매칭시키는 쿼리이다. 점 표기법을 사용한다. 점표기법을 사용하는 중첩된 필드를 다룰 때는 꼭 더블쿼테이션 마크 안에 필드를 넣어야한다.

 

db.testcollection.find({"address.si":"seoul"})

 

임베디드 되거나 중첩된 필드에도 여러 연산자 사용이 가능하다.

 

> db.testcollection.find({"address.si":{$gt:"a"}})

 

하나의 연산자 예제이며 다양한 연산자($or,and 등)적용이 가능하다.

 

-Query an Array

배열에 관한 쿼리를 다루기 전에 밑의 문서들을 삽입하자.

 

1
2
3
4
5
6
7
db.inventory.insertMany([
   { item: "journal", qty: 25, tags: ["blank", "red"], dim_cm: [ 14, 21 ] },
   { item: "notebook", qty: 50, tags: ["red", "blank"], dim_cm: [ 14, 21 ] },
   { item: "paper", qty: 100, tags: ["red", "blank", "plain"], dim_cm: [ 14, 21 ] },
   { item: "planner", qty: 75, tags: ["blank", "red"], dim_cm: [ 22.85, 30 ] },
   { item: "postcard", qty: 45, tags: ["blue"], dim_cm: [ 10, 15.25 ] }
]);
cs

 

-Match an Array

배열에서 동등 조건을 지정하려면 쿼리 문서 {<field> : <value>}를 사용한다. 여기서 <value>는 요소의 순서를 포함하여 일치시킬 정확한 배열이다.

 

> db.inventory.find({tags:["red","blank"]})

 

위 쿼리는 tags라는 배열에 red와 blank가 포함되있으며 순서까지 일치하는 배열을 가진 문서를 조회한다. red와 blank 순서를 바꾸면 결과값이 다르게 나온다.

 

만약 순서와 상관없이 조건에 해당되는 배열 요소만 가지고 있으면 쿼리조건에 부합하는 결과를 가져오는 쿼리는 아래와 같은 연산자($all)를 이용한다.

 

> db.inventory.find({tags:{$all:["red","blank"]}})

 

이 쿼리는 red와 blank 요소를 가지기만 하면된다. 즉, 위 요소 이외에 다른 요소가 있어도 위 두개만 가지고 있으면 쿼리 결과로 반환된다.

 

-Query an Array for an Element

배열 필드에 조건에 명시한 요소 하나만 있어도 결과값으로 노출시키고 싶다면 아래와 같은 쿼리를 작성한다.

 

> db.inventory.find({tags:"red"})

 

tags의 요소로 "red"를 대괄호로 묶지 않으면 된다.

 

> db.inventory.find( {dim_cm:{$gt:25}} )

 

배열 필드도 역시 다양한 연산자 적용이 가능하다.

 

-Specify Multiple Conditions for Array Elements

배열도 다수의 조건을 조합한 쿼리 필터 지정이 가능하다. 간단한 예제를 살펴보자.

 

Query an Array with Compound Filter Conditions on the Array Elements

> db.inventory.find({dim_cm:{$gt:15,$lt:20}})

 

 

위 쿼리절은 dim_cm이 15보다 큰 요소가 있거나 20보다 작은 배열의 요소가 하나라도 매칭되면 쿼리 결과로 반환된다.(OR 조건이 된다.)

즉, 각 조건에 대한 or 조건이 되는 것이다. 하지만 모든 조건을 충족하는 요소를 찾길 원한다면 아래와 같은 연산자를 사용한다.

 

Query for an Array Element that Meets Multiple Criteria

>db.inventory.find({dim_cm:{$elemMatch:{$gt:22,$lt:30}}})

 

 

위 쿼리는 22보다 크거나 30보다 작은 두개의 조건을 모두 만족하는 배열의 요소가 하나라도 있으면 결과값에 포함시킨다.($elemMatch)

 

Query for an Element by the Array index Position

점 표기법을 사용하면 배열의 특정 인덱스 또는 위치에서 요소에 대한 쿼리 조건을 지정할 수 있다. 배열은 0부터 시작하는 인덱싱을 사용한다.

 

> db.inventory.find({"dim_cm.1":{$gt:25}})

 

Query an Array by Array Lenght

쿼리 조건으로 배열의 요소수로 조건 쿼리가 가능하다.

 

> db.inventory.find({tags:{$size:3}})

 

위 쿼리는 tags 필드 중 요소를 3개 가지고 있는 문서를 결과값에 포함시킨다.

 

 

생각보다 포스팅이 길어져 여기서 한번 끊고 2번 포스팅으로 넘어가려고 합니다. 다음 포스팅은 임베디드된 필드 배열에 관한 쿼리로 시작합니다.

posted by 여성게
: