Last Updated on 1월 29, 2024 by Jade(정현호)
안녕하세요
이번 포스팅은 MongoDB in Action(몽고디비 인 액션) 2nd Edition 책의 내용을 정리한 글입니다
이전의 포스팅과도 연관된 글로 참조하시면 됩니다.
Contents
데이터베이스, 컬렉션, 도튜먼트
MongoDB 는 저장된 정보(도큐먼트)를 JSON(JavaScript Object Notation) 형태로 표현하는 데이터베이스입니다.
JSON 은 JavaScript Object Notation 줄임말로 속성-값 쌍(attribute–value pairs), 배열 자료형(array data types) 또는 기타 모든 시리얼화 가능한 값(serializable value) 또는 "키-값 쌍"으로 이루어진 데이터 오브젝트를 전달하기 위해 인간이 읽을 수 있는 텍스트를 사용하는 개방형 표준 포맷입니다.
MongoDB에 데이터를 입력,변경 또는 저장된 데이터를 출력시에 JSON 표현식이 사용됩니다.
데이터의 입력,수정, 조회 시에 사람이 이해할 수 있는 JSON 으로 처리 및 표현되지만, MongoDB에서 실제로 저장될 때에는 BSON이라고 하는 Binary JSON 포맷으로 저장이 됩니다.
JSON 과 같이 텍스트 그 자체로 저장할 경우 속도 와 저장공간 측면에서 불리한 하기 때문에 Binary JSON 포맷을 사용합니다.
BSON은 JSON을 Binary 로 저장하는 것이외에도, JSON 보다 더 많은 데이터 유형을 저장할 수 있습니다.
BSON에서 사용 가능한 데이터 타입에 대한 정보는 아래 문서에서 확인할 수 있습니다.
BSON 에서 사용할 수 있는 document 의 최대 크기는 16MB 입니다.
도큐먼트(document) 는 RDBMS의 row 와 같은 의미를 가집니다.
최대 도큐먼트(Document) 크기 제한은 단일 도큐먼트(Document)가 과도한 양의 RAM 또는 전송 중에 과도한 양의 대역폭을 사용하지 않도록 하는 데 도움이 됩니다. 최대 크기보다 큰 문서를 저장하기 위해 MongoDB는 GridFS API를 제공합니다.
GridFS에 대한 자세한 내용은 mongofiles 및 드라이버 설명서를 참조하십시오.
사용자(user) 정보와 주문(order) 정보 와 같이 다른 유형의 도큐먼트들을 별도의 공간에 저장하고자 할 수 있습니다
이는 MonogDB가 RDBMS의 테이블과 같이 도큐먼트들을 그루핑(Grouping) 할 수 있는 방법이 필요함을 뜻하며, 이를 컬렉션(collection) 이라고 부릅니다
RDBMS 와 MongoDB 에서 사용하는 용어를 비교한 것입니다.
RDBMS | MongoDB |
database | database |
table | collection |
index | index |
row | BSON document |
column | BSON field |
join | embedding 또는 linking |
Namespace(네임스페이스)
MongoDB 의 컬렉션 또는 인덱스에 대한 정식 이름입니다. 네임스페이스는 다음과 같이 데이터베이스 이름과 컬렉션 또는 인덱스 이름의 조합입니다
[database-name].[collection-or-index-name].
그리고 모든 도큐먼트 네임스페이스에 속하게 됩니다.
MongoDB는 컬렉션들을 별개의 데이터베이스(database)에 분리합니다.
다른 SQL제품에서의 데이터베이스들과 다르게, MongoDB에 있는 데이터베이스들은 단지 컬렉션들을 구분하는 네임스페이스 일 뿐 입니다.
MongoDB 에 질의하기 위해서 질의를 하기를 원하는 대상 도큐먼트가 존재하는 데이터베이스(또는 네임스페이스)와 컬렉션을 알아야 합니다.
쉘을 시작할 때 데이터베이스를 지정하지 않으면 test 라는 이름의 기본 설정 데이터베이스에 연결됩니다.
다른 네임스페이스에 연결하기 위해서는 아래와 같이 데이터베이스를 변경할 수 있습니다.
> use test switched to db test
접속한 MongoDB 에 존재하는 데이터베이스에 대한 정보는 아래와 같이 조회를 해볼 수 있습니다.
> show dbs admin 0.000GB config 0.000GB local 0.000GB
왜 MongoDB는 데이터베이스와 컬렉션 모두를 다 가지고 있을까요?
이에 대한 대답은 어떻게 MongoDB가 데이터를 디스크에 쓰는지에 달려 있습니다.
데이터베이스의 모든 컬렉션들이 같은 파일에 그룹핑 되는데, 이는 같은 데이터베이스내 관련된 컬렉션들을 보관하기 위함이며, 이는 메모리 관점에서 이치에 맞는 것이라고 볼 수 있습니다
Note
명령어 라인에서 ">" 는 명령어라인의 프롬프트로 명령어와 수행 결과의 차이를 구분하게 됩니다.
데이터베이스와 컬렉션의 생성
위의 예제에서 데이터베이스를 생성하지 않았는데도 어떻게 그 데이터베이스로 변경할 수 있는지 의아했을지도 모릅니다.
사실 MongoDB 에서는 데이터베이스를 만드는 것이 필요 없습니다.
데이터베이스와 컬렉션은 도큐먼트가 처음 입력될 때 생성됩니다.
이것은 데이터에 대한 MongoDB의 동적 접근 방식과 동일 합니다.
도큐먼트의 구조가 미리 정의될 필요가 없듯이 개별 컬렉션과 데이터베이스도 런타임시 생성되게 됩니다.
이렇게 함으로써 개발 과정이 단순해지고 빨라지며, 본질적으로 동적 네임스페이스 할당을 충분히 활용하게 됩니다.
다만 계정에서 데이터베이스나 컬렉션을 생성할 수 있는 권한이 있어야 가능합니다. 읽고 쓰기 권한(CRDU)만 있다면 컬렉션 생성은 불가합니다.
데이터베이스나 컬렉션이 우연히 생성될지가 우려된다면 드라이버에서 지원되는 strict 모드를 통해 그러한 실수를 미연에 발지 할 수 있습니다.
이제 첫 번째 도큐먼트를 만들 시간이 되었습니다. 우리가 자바스크립트 쉘을 사용하고 있으므로 도큐먼트는 JSON 형태로 표현됩니다.
먼저 데이터베이스가 test 가 아니라면 test 로 변경합니다.
> use test switched to db test
컬렉션 생성 과 삭제
데이터 입력 없이 빈 컬렉션(collection)을 생성하기 위해서는 다음과 같이 명령어를 수행합니다.
• collection 생성
> db.createCollection("collection");
생성된 컬렉션 삭제는 다음과 같이 drop() 메서드를 이용합니다.
• collection 삭제
> db.collection명.drop();
데이터 조회, 입력, 변경, 삭제
이번 단계에서는 MongoDB 에 데이터를 입력, 조회, 변경 등을 진행하도록 하겠습니다.
도큐먼트를 저장하기 위해서는 먼저 컬렉션(collection) 을 지정해야 합니다.
(컬렉션 = RDBMS의 테이블)
데이터 입력
데이터의 입력은 insertOne(또는 insertMany) 를 사용하면 되며, insertOne 메서드를 통해 아래와 같이 users 컬렉션 으로 저장하도록 하겠습니다.
명령어: db.users.insertOne({username:"smith"})
> db.users.insertOne({username:"smith"}) { "acknowledged" : true, "insertedId" : ObjectId("61ae69a5986719224adc57d0") }
* 참고: { acknowledged ....} 이부분은 위에서 실행한 insertOne 에 대한 결과 내용입니다.
위의 코드를 입력하고 실행하고 나면 약간 지연되는 느낌을 가질 수도 있습니다.
이 코드를 실행하는데 까지는 test 데이터베이스와 users 컬렉션이 하드디스크에 아직 생성되지 않은 상태임으로 초기 데이터파일을 할당하느라 약간의 지연을 느낄 수도 있습니다
위의 삽입문이 성공적으로 수행되면 첫 번째 도큐먼트가 저장된 것입니다.
MongoDB 기본 설정에서 이제 데이터는 쉘이 중단하거나 갑자기 수행 중인 작업 머신을 재기동 하더라도 삽입이 보장된다는 뜻입니다.
새로 삽입된 도큐먼트를 확인하기 위해서는 다음과 같은 간단한 쿼리를 실행할 수 있습니다.
> db.users.find() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith" }
참고: insertOne 은 대소문자를 구분합니다.
도큐먼트 필드에 _id라는 필드가 추가된 것을 먼저 확인해보시면 됩니다.
_id 는 도큐먼트의 프라이머리 키 라고 생각 하면 됩니다.
모든 MongoDB는 도큐먼트의 _id 필트가 필요하며, 도큐먼트가 생성될 때 이 필드가 없으면 MongoDB 객체 ID(Object ID) 라는 특별한 값을 생성해서 도큐먼트에 자동으로 추가하게 됩니다.
컬렉션 내에서 객체 ID는 고유한 값을 가져야만 하며, 이 필드에 대해서 유일하게 요구되는 사항입니다.
삽입을 통해서 직접 _id 값을 넣을 수 있으며 객체 ID는 MongoDB 의 기본 설정입니다.
두번째 사용자를 추가해보도록 하겠습니다.
> db.users.insertOne({username:"jones"}) { "acknowledged" : true, "insertedId" : ObjectId("61ae6a78986719224adc57d1") }
이제 컬렉션(테이블)에 두개의 도큐먼트(로우,row)가 있을 것입니다.
count 메서드로 확인해보겠습니다.
> db.users.count() 2
데이터 조회
컬렉션에 하나 이상의 도큐먼트가 있으므로 MongoDB에서는 더 정교한 쿼리를 실행 할 수 있도록 기능을 지원 하고 있습니다.
컬렉션에 있는 도큐먼트를 조회해 보기 위해서는 find() 메서드를 사용하면 됩니다.
> db.users.find() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith" } { "_id" : ObjectId("61ae6a78986719224adc57d1"), "username" : "jones" }
find() 메서드에는 RDB 에서의 SELECT 명령어와 동일하다고 할 수 있으며, find 메서드는 아래와 같은 구조로 되어 있습니다.
db.collection.find( query, projection ).options
• Parameter 및 Description
- query : 쿼리 연산자(operator) 를 사용하여 특정 선택 필터를 지정합니다. 조회하고자 하는 조건을 입력하는 부분으로 컬렉션 내에 모든 도큐먼트를 조회하려면 해당 파라미터는 별도로 입력하지 않으면 됩니다.
- projection : 쿼리 필터와 일치하는 문서에서 조회결과를 반환할 필드를 지정합니다. 즉 보고자 하는 별도의 필드(컬럼)만을 입력하여 해당 필드만 보고자 할 경우 입력하는 파라미터입니다.
- options : 쿼리에 대한 추가 옵션을 지정합니다. 지정된 옵션은 쿼리 동작과 반환되는 방식을 수정(결정) 합니다.
예를 들어 정렬을 하는 sort , 도큐먼트를 몇개만 읽어 올지를 지정하는 limit 등과 같은 옵션이 있습니다.
옵션에 대한 더 추가적인 내용은 다음 문서를 참조하시면 됩니다.
query
위의 find 메서드에 특정 조건을 쿼리 연산자(query operator) 로 넘겨줄 수 있습니다.
쿼리 연산자(query operator) 는 컬렉션에 있는 모든 도큐먼트에 대해 주어진 조건이 일치 여부를 검사합니다.
사용자 이름 jones 인 도큐먼트만 보기 위해서는 다음과 같이 도큐먼트를 쿼리 연산자(query operator)로 넘겨줍니다.
> db.users.find({username: "jones"}) { "_id" : ObjectId("61ae6a78986719224adc57d1"), "username" : "jones" }
{username: "jones"} 는 사용자 이름(username)이 jones 인 모든 도큐먼트를 반환해 주며, 존재하는 모든 도큐먼트에 대해서 username 키의 값이 문자적으로 일치하는지 여부를 검사합니다.
어떤 매개변수(parameter) 없이 find 메서드를 호출하는 것은 비어 있는 조회 조건 명령어를 넘겨주는 것과 동일 합니다.
이말은 db.users.find() 는 db.users.find({}) 와 동일 함을 뜻 합니다.
AND 와 OR 연산
필드 사이에 명시적으로 AND 를 만들어 내기 위해서는 쿼리 조회 조건에 여러 개의 필드를 명시할 수 있습니다.
> db.users.find({ _id:ObjectId("61ae69a5986719224adc57d0"), username:"smith" }) { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith" }
위의 쿼리에서 필드들을 AND 연산을 수행 함으로 _id 와 username 필드를 모두 일치하는 도큐먼트를 찾는 것 입니다.
또한 명시적으로 $and 연산자를 사용할 수 있습니다.
> db.users.find({ $and: [ { _id: ObjectId("61ae69a5986719224adc57d0")}, { username: "smith"} ]}) { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith" }
OR 연산자를 이용해서 도큐먼트를 선택하는 것도 비슷합니다 그저 $or 연산자만 사용하면 됩니다.
> db.users.find({ $or:[ { username: "smith"}, { username: "jones"} ]}) { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith" } { "_id" : ObjectId("61ae6a78986719224adc57d1"), "username" : "jones" }
조회 결과로 smith 와 jones 의 도큐먼트가 반환하였고, 조회 그 자체가 하나의 도큐먼트입니다.
projection
projection 을 사용하여 특정 컬럼을 보여주거나 빼고 조회해볼 수도 있습니다.
테스트를 위해서 1개 도큐먼트를 입력해보겠습니다.
> db.users.insertOne({username:"jade",address:"대한민국 서울",job:"dba"}) { "acknowledged" : true, "insertedId" : ObjectId("63c3ac8100cd137c91e0d0e1") }
필드명:1 은 해당 필드만 출력을 의미하며, 필드명:0은 해당 필드를 제외하고 출력을 의미합니다.
아래와 같이 1 을 사용하여 조회하면 해당 필드만 출력되게 됩니다.
> db.users.find({username:'jade'},{username:1}) { "_id" : ObjectId("63c3ac8100cd137c91e0d0e1"), "username" : "jade" }
0 을 사용하여 조회하면 해당 필드를 제외하고 출력 됩니다.
> db.users.find({username:'jade'},{username:0}) { "_id" : ObjectId("63c3ac8100cd137c91e0d0e1"), "address" : "대한민국 서울", "job" : "dba" }
기본적으로 출력되는 "_id" 값을 출력하지 않으려고 한다면 아래와 같이 _id:0 을 입력하면 해당 필드는 출력 되지 않습니다.
> db.users.find({username:'jade'},{_id:0}) { "username" : "jade", "address" : "대한민국 서울", "job" : "dba" }
options
옵션을 사용하여 쿼리의 동작과 결과를 반환하는 방식을 바꿔서 조회할 수 있습니다.
옵션은 다양한 종류가 있으며, 옵션 별로 파라미터가 조금씩 다를 수 있으므로 매뉴얼에서 내용을 확인 해보시면 됩니다.
옵션 중에서 몇 개를 살펴보도록 하겠습니다.
sort
sort 는 정렬을 하기 위한 옵션 기능입니다. MongoDB에 저장된 도큐먼트의 정렬이 아닌 출력할 때 결과에 대한 정렬을 의미합니다.
필드명:1 또는 필드명:-1 으로 사용할 수 있습니다.
1 이면 오름차순이며, -1 이면 내림차순을 의미합니다.
> db.users.find({},{_id:0}).sort({username:1}) { "username" : "jade", "address" : "대한민국 서울", "job" : "dba" } { "username" : "jones" } { "username" : "smith" } > db.users.find({},{_id:0}).sort({username:-1}) { "username" : "smith" } { "username" : "jones" } { "username" : "jade", "address" : "대한민국 서울", "job" : "dba" }
limit
limit 은 출력 결과를 제한하는 기능 옵션입니다.
> db.users.find({},{_id:0}).limit(1) { "username" : "smith" }
위와 같이 출력 결과를 제한 합니다.
MySQL 에서의 limit 과 동일한 기능이라고 할 수 있습니다. MySQL 에서도 order by 와 limit 을 같이 사용하는 것 처럼 MongoDB 에서도 같이 sort 와 limit 을 같이 사용하는 것이 더 활용도가 높다고 할 수 있습니다.
> db.users.find({},{_id:0}).sort({name:-1}).limit(2) { "username" : "smith" } { "username" : "jones" }
도큐먼트로 명령어를 표현하는 발상은 MongoDB를 사용할 때 사용되는 방식이지만 관계형 데이터베이스에 익숙해져 있는 사용자에게는 익숙하지 않은 일이긴 합니다.
이러한 방식은 조회가 SQL 문자열이 아닌 도큐먼트이므로 애플리케이션에서 조회를 프로그래밍하기 더 쉽다는 장점이 있습니다.
projection 과 options 을 확인해보기 위해 입력한 도큐먼트를 다시 삭제를 해두겠습니다.
> db.users.deleteOne({username:"jade"}) { "acknowledged" : true, "deletedCount" : 1 }
* delete 에 대한 내용은 아래에서 더 자세하게 설명됩니다.
집계(Grouping) 조회 기능에 대한 내용은 별도 작성된 포스팅을 참조하시면 됩니다.
MongoDB의 다양한 데이터 모델 설계 및 조회에 관한 내용은 다음 포스팅을 참조하시면 됩니다.
업데이트 연산자
업데이트는 기존의 도큐먼트의 내용을 변경하는 작업으로 도큐먼트의 키의 유무에 따라서 업데이트 되는 내용이 달라지게 됩니다.
먼저 테스트할 데이터는 아래와 같습니다.
> db.users.find() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith" } { "_id" : ObjectId("61ae6a78986719224adc57d1"), "username" : "jones" }
smith 라는 사용자에게 거주 국가 정보를 추가한다고 가정해보면 아래와 같이 updateOne 이나 updateMany 문에 $set 절을 사용하여 추가할 수 있게 됩니다
> db.users.updateOne({ username:"smith"}, {$set: {country: "Canada"}}) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 } 또는 > db.users.updateMany({ username:"smith"}, {$set: {country: "Canada"}})
업데이트 된 내용은 아래와 같습니다
> db.users.find() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith", "country" : "Canada" } { "_id" : ObjectId("61ae6a78986719224adc57d1"), "username" : "jones" }
추가로 username: smith 가 아닌 모든 유저에 대해서 수행하고자 한다면 다음과 같이 필터에 {} 을 사용하면 됩니다.
> db.users.updateMany({ }, {$set: {country: "Canada"}})
두번째 업데이트 방식은 update 메서드를 사용하였을 경우 도큐먼트의 필드 값을 추가(설정) 하는 것이 아니라 기존의 도큐먼트 값을 다른 것으로 변경하는 것입니다.
이는 이전의 update 메서드에서 $set 연산자 를 사용하는 업데이트를 의도했을 때 종종 발생되는 실수 이기도 합니다.
> db.users.update ({username:"jones"}, {country:"Netherlands"}) > db.users.find() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith", "country" : "Canada" } { "_id" : ObjectId("61ae6a78986719224adc57d1"), "country" : "Netherlands" }
위의 결과와 같이 {username:"jones"} 의 값이 {country:"Netherlands"} 으로 업데이트가 되었습니다.
위의 update 메서드는 deprecated 되었고, 대체된 명령어로는 replaceOne 메서드를 사용하면 됩니다.
> db.users.replaceOne({username:"jones"}, {country:"Netherlands"})
다시 테스트를 위해서 이번에는 username: jones 를 추가하겠습니다.
> db.users.updateOne({country: "Netherlands"}, {$set: {username: "jones"}}) 또는 > db.users.updateOne({country: "Netherlands"}, {$set: {username: "jones"}}) // 다시 조회 // > db.users.find() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith", "country" : "Canada" } { "_id" : ObjectId("61ae6a78986719224adc57d1"), "country" : "Netherlands", "username" : "jones" }
다시 updateOne(또는 updateMany) 메서드를 사용하여 업데이트를 진행해보도록 하겠습니다.
> db.users.updateOne({ username:"smith"}, {$set: {country: "France"}}) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
그리고 다시 조회해 보겠습니다.
> db.users.find() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith", "country" : "France" } { "_id" : ObjectId("61ae6a78986719224adc57d1"), "country" : "Netherlands", "username" : "jones" }
위와 같이 보통의 SQL에서 사용하는 것처럼 업데이트가 수행되었습니다.
추가로 또 업데이트를 하겠으며 이번에는 업데이트할 키는 city 입니다.
추가로 또 업데이트를 하겠으며 이번에는 업데이트할 키는 city 입니다. > db.users.updateOne({ username:"smith"}, {$set: {city: "Paris"}}) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 } // 조회 // > db.users.find() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith", "country" : "France", "city" : "Paris" } { "_id" : ObjectId("61ae6a78986719224adc57d1"), "country" : "Netherlands", "username" : "jones" }
즉, 키가 존재한다면 키를 새로운 내용으로 업데이트를 하고, 없다면 RDB에서의 컬럼 추가 형태가 되게 됩니다.
이전의 update 메서드로는 아래와 같이 되며 동일한 결과가 됩니다
> db.users.update({ username:"smith"}, {$set: {city: "Paris"}})
방금 추가한 city 키가 더이상 필요 없다면 $unset 연산자로 삭제 할 수 있습니다.
> db.users.updateMany({username: "smith"}, {$unset: {city:1}}) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 } // 다시 조회 // > db.users.find() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith", "country" : "France" } { "_id" : ObjectId("61ae6a78986719224adc57d1"), "country" : "Netherlands", "username" : "jones" }
복잡한 데이터 업데이트
지금까지 테스트한 도큐먼트를 조금 더 복잡하게 업데이트 해보려고 합니다.
아래와 같이 자신이 좋아하는 것의 목록을 추가로 저장하려고 한다면 어떻게 해야 할까요?
username : "smith" favorites : { cities: ["Chicago","Cheyenne"], movies: ["Casablanca","For a Few Dollars More","The Sting"] } }
favorites 키의 값은 또 다른 객체이며, 이 객체에는 좋아 하는 도시 와 좋아하는 영화 에 대한 리스트가 있습니다.
아래와 같이 업데이트를 수행 할 수 있습니다.
> db.users.updateMany( {username: "smith"}, { $set: { favorites: { cities: ["Chicago","Cheyenne"], movies: ["Casablanca","For a Few Dollars More","The Sting"] } } } ) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
들여쓰기는 필수사항은 아니지만 이런 도큐먼트 형식에서 가독성을 높여 주기 때문에 가급적 사용하는 편이 좋을 것 같습니다.
이번에는 jones 에게도 좋아하는 영화 내용을 추가해보도록 하겠습니다.
> db.users.updateMany({username: "jones"}, { $set: { favorites: { movies: ["Titanic","Rocky"] } } } ) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
업데이트가 되었기 때문에 조회를 하겠으며, 이번에는 pretty 연산자를 사용해보도록 하겠습니다.
> db.users.find().pretty() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith", "country" : "France", "favorites" : { "cities" : [ "Chicago", "Cheyenne" ], "movies" : [ "Casablanca", "For a Few Dollars More", "The Sting" ] } } { "_id" : ObjectId("61ae6a78986719224adc57d1"), "country" : "Netherlands", "username" : "jones", "favorites" : { "movies" : [ "Titanic", "Rocky" ] } }
pretty 연산자를 사용하면 서버로 부터 반환된 결과를 잘 정돈된 형태로 반환해주게 됩니다.
엄밀히 말해서 pretty() 는 실제로는 cursor.pretty() 이며, 이는 조회 결과를 가독성 있게 잘 정돈된 형태로 보여주기 위해서 커서를 설정하는 방법입니다.
여기에서 영화 카사블랑카(Casablanca) 를 좋아하는 모든 사용자를 검색하기 위해서는 아래와 같이 할 수 있습니다.
> db.users.find({"favorites.movies": "Casablanca"}).pretty() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith", "country" : "France", "favorites" : { "cities" : [ "Chicago", "Cheyenne" ], "movies" : [ "Casablanca", "For a Few Dollars More", "The Sting" ] } }
favorites 와 movies 사이에 점(dot) 은 쿼리 엔진으로 하여금 favorites 라는 이름의 키를 찾고, 그 다음 이 키의 값의 객체에서 다시 movies 라는 이름의 키의 값이 되는 객체를 찾도록 지시하게 됩니다.
따라서 배열내의 어떤 요소든 쿼리와 일치하는 부분이 있다면 배열 상의 쿼리는 매칭됨으로 쿼리는 사용자를 찾아주게 됩니다.
더 발전된 업데이트
영화 카사블랑카를 좋아하는 사용자가 라이언 일병 구하기(Saving Private Ryan) 도 좋아한다고 할 때 이 정보를 업데이트를 하기 위해서는 어떻게 해야 할까요?
$set 연산자를 사용할 수도 있겠지만 이것은 movies 배열 전체에 대해서 다시 쓰기를 해야 합니다.
movies 리스트에 하나의 값만 추가하려고 하는 것임으로 이때는 $push 나 $addToSet 을 사용하는 것이 좋습니다
$push 나 $addToSet 두가지 연산자 모두 배열에 새로운 아이템을 추가하는 것이며, $addToSet 은 값을 추가할 때 중복되지 않도록 확인하게 됩니다.
db.users.updateMany ({"favorites.movies":"Casablanca"}, { $addToSet: {"favorites.movies": "Saving Private Ryan"} }, false, true ) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
첫번째 인자는 쿼리 셀렉터로 조회 조건이며, 두번째는 $addToSet 연산자를 이용하여 "Saving Private Ryan" 를 리스트에 추가하는 것입니다.
세번째는 false 인데 upsert(update & insert) 가 허용되는지 아닌지를 설정하는 항목으로 해당하는 도큐먼트가 존재하지 않을 때 입력(insert)를 해야 하는지를 결정하는 내용입니다.
네번째 true 는 이 업데이트가 다중 업데이트 일 경우 하나 이상의 도큐먼트에 대해서도 업데이트가 이루어져야 함을 의미합니다.
MongoDB 에서는 쿼리 셀렉터의 조건에 맞는 첫번째 도큐먼트 대해서만 업데이트를 하는 것이 기본설정으로 되어 있습니다.
조건에 일치하는 모든 도큐먼트에 대해서 업데이트 연산을 하려면 네번째 인자를 반드시 명시적으로 지정해야 합니다.
다시 조회하면 아래와 같습니다.
> db.users.find({"favorites.movies": "Saving Private Ryan"}).pretty() { "_id" : ObjectId("61ae69a5986719224adc57d0"), "username" : "smith", "country" : "France", "favorites" : { "cities" : [ "Chicago", "Cheyenne" ], "movies" : [ "Casablanca", "For a Few Dollars More", "The Sting", "Saving Private Ryan" ] } }
데이터 삭제
MongoDB 쉘을 통해 기본적인 생성, 읽기, 업데이트를 해봤고 이제 마지막으로 가장 간단한 연산인 삭제를 해봅시다.
deleteMany() 를 사용 시 매개변수가 주어 지지 않으면 컬렉션의 모든 도큐먼트 삭제합니다
deleteMany는 조건에 만족하는 모든 다큐먼트를 삭제하게 되고, deleteOne은 조건에 만족하는 첫 번째 도큐먼트만 삭제합니다.
예를 들어 foo 컬렉션의 모든 도큐먼트를 지우기 위해서는 다음과같이 별도의 매개변수를 사용하지 않고 실행하면 됩니다.
> db.foo.deleteMany({})
이전의 Deprecated 된 명령어인 remove 는 아래와 같이 사용합니다.
> db.foo.remove({})
일부의 도큐먼트를 지워야 할 때는 () 에 조건을 입력하여 진행합니다.
좋아하는 도시 리스트에 있는 Cheyenne 이 들어가 있는 사용자를 모두 삭제하는 쿼리는 아래와 같습니다.
> db.users.deleteMany({"favorites.cities":"Cheyenne"}) { "acknowledged" : true, "deletedCount" : 1 } > db.users.find().pretty() { "_id" : ObjectId("61c5360828d7c8dece661c0c"), "country" : "Netherlands", "username" : "jones", "favorites" : { "movies" : [ "Titanic", "Rocky" ] } }
deleteOne은 조건에 만족하는 첫 번째 도큐먼트만 삭제합니다.
아래에서는 중복된 도큐먼트 3건을 입력하였습니다.
## 중복되는 도큐먼트 3건을 입력 > db.users.insertOne({username:"jade",address:"대한민국 서울",job:"dba"}) > db.users.insertOne({username:"jade",address:"대한민국 서울",job:"dba"}) > db.users.insertOne({username:"jade",address:"대한민국 서울",job:"dba"}) ## 데이터 조회 > db.users.find({},{_id:0}) { "username" : "smith" } { "username" : "jones" } { "username" : "jade", "address" : "대한민국 서울", "job" : "dba" } { "username" : "jade", "address" : "대한민국 서울", "job" : "dba" } { "username" : "jade", "address" : "대한민국 서울", "job" : "dba" }
deleteOne 메서드를 이용해서 삭제를 진행 해보겠습니다.
> db.users.deleteOne({username:"jade"}) { "acknowledged" : true, "deletedCount" : 1 } > db.users.find({},{_id:0}) { "username" : "smith" } { "username" : "jones" } { "username" : "jade", "address" : "대한민국 서울", "job" : "dba" } { "username" : "jade", "address" : "대한민국 서울", "job" : "dba" }
중복된 3건 중에서 1건만 삭제된 것을 확인할 수 있습니다.
remove() 나 deleteMany 연산은 컬렉션을 지우지 않는다는 점을 명심해야 합니다.
컬렉션내에 존재하는 도큐먼트를 지울 뿐인데, 이것은 SQL에서 DELETE 명령어와 비슷합니다.
컬렉션과 포함된 모든 인덱스를 같이 삭제하려면 drop() 메서드를 사용하면 됩니다
RDBMS 에서 drop table [테이블명] 명령을 수행하는 것과 동일 합니다.
명령어 구문: db.컬렉션명.drop()
> db.users.drop()
변경된 CRUD API
MongoDB 3.2 버전 부터 CRUD 관련 명령에 대한 변경이 있습니다.
New API
|
Description
|
---|---|
Equivalent to initializing |
|
Equivalent to
db.collection.remove() . |
|
Equivalent to
|
|
Equivalent to
db.collection.findAndModify() method with remove set to true. |
|
Equivalent to
db.collection.findAndModify() method with update set to a replacement document. |
|
Equivalent to
db.collection.findAndModify() method with update set to a document that specifies modifications using update operators. |
|
Equivalent to
db.collection.insert() method with an array of documents as the parameter. |
|
Equivalent to
db.collection.insert() method with a single document as the parameter. |
|
Equivalent to
db.collection.update( <query>, <update> ) method with a replacement document as the <update> parameter. |
|
Equivalent to
db.collection.update( <query>, <update>, { multi: true, ... }) method with an <update> document that specifies modifications using update operators and the multi option set to true. |
|
Equivalent to
db.collection.update( <query>, <update> ) method with an<update> document that specifies modifications using update operators. |
자세한 내용은 릴리즈 노트를 참조하시면 됩니다.
help - 도움말 얻기
먼저 쉘은 MongoDB 로 쉽게 작업할 수 있도록 환경을 제공하고 있습니다.
위 아래 방향키를 사용하여 이전 쿼리를 다시 활용할 수 있고, 컬렉션 이름과 같은 것에 자동완성 기능을 사용할 수 있습니다.
자동완성 기능은 자동완성을 수행하거나 가능성 있는 결과물을 보여 주기 위해 탭 키를 사용합니다.
또한 다음과 같은 방법으로 쉘에서 더 많은 정보를 얻을 수 있습니다.
help
많은 함수가 그들 자신을 설명하는 help 메세지를 보기 좋은 형태로 출력합니다.
> db.help() DB methods: db.adminCommand(nameOrDocument) - switches to 'admin' db, and runs command [just calls db.runCommand(...)] db.aggregate([pipeline], {options}) - performs a collectionless aggregation on this database; returns a cursor db.auth(username, password) db.cloneDatabase(fromhost) - will only function with MongoDB 4.0 and below db.commandHelp(name) returns the help for the command db.copyDatabase(fromdb, todb, fromhost) - will only function with MongoDB 4.0 and below db.createCollection(name, {size: ..., capped: ..., max: ...}) db.createUser(userDocument) db.createView(name, viewOn, [{$operator: {...}}, ...], {viewOptions}) db.currentOp() displays currently executing operations in the db db.dropDatabase(writeConcern) db.dropUser(username) db.eval() - deprecated db.fsyncLock() flush data to disk and lock server for backups
참고로 쿼리에 대한 실행 계획이나 성능에 대한 도움은 explain 이라는 별도의 함수를 호출하여 확인할 수 있습니다.
MongoDB 쉘을 시작할 때 사용할 수 있는 옵션 또한 존재합니다.
이에 대한 리스트를 화면을 보여주기 위해서는 아래와 같이 명령을 수행합니다.
$ mongo --help
그 다음 컬렉션에 대해서도 help 함수를 사용하여 수행가능한 메서드를 확인할 수 있습니다
> db.users.help() DBCollection help db.users.find().help() - show DBCursor help <... 중략 ...> db.users.getShardDistribution() - prints statistics about data distribution in the cluster db.users.getSplitKeysForChunks( <maxChunkSize> ) - calculates split points over all chunks and returns splitter function db.users.getWriteConcern() - returns the write concern used for any operations on this collection, inherited from server/db if set db.users.setWriteConcern( <write concern doc> ) - sets the write concern for writes to the collection db.users.unsetWriteConcern( <write concern doc> ) - unsets the write concern for writes to the collection db.users.latencyStats() - display operation latency histograms for this collection
위에서 언급한 자동 탭 완성 기능도 내장되어 있으므로 메서드의 첫 번째 문자를 입력하고 탭 키를 두 번 치면 입력한 문자로 시작되는 메서드의 리스트를 볼 수 있습니다.
다음의 예는 탭 완성 기능을 사용해서 get 으로 시작되는 컬렉션의 메서드를 보여 줍니다.
> db.users.get db.users.getCollection( db.users.getIndexKeys( db.users.getIndices( db.users.getPlanCache( db.users.getShardDistribution( db.users.getSplitKeysForChunks( db.users.getDB( db.users.getIndexSpecs( db.users.getMongo( db.users.getQueryOptions( db.users.getShardVersion( db.users.getWriteConcern( db.users.getFullName( db.users.getIndexes( db.users.getName( db.users.getSecondaryOk( db.users.getSlaveOk(
이번 글에서는 실제적인 도큐먼트 데이터 모델을 소개했고, 도큐먼트에 대해 여러 가지 다양 하고 자주 쓰이는 MongoDB 쿼리 와 연산을 확인해 보았으며 도움말이 필요한 경우 찾아볼 수 있는 몇 가지 방법을 살펴봤습니다.
Reference
Reference Book
• MongoDB in Action(몽고디비 인 액션) 2nd Edition
Reference Link
• mongodb.com/mongo-shell-and-crud-api
다음 이어서 볼만한 글
연관된 다른 글
Principal DBA(MySQL, AWS Aurora, Oracle)
핀테크 서비스인 핀다에서 데이터베이스를 운영하고 있어요(at finda.co.kr)
Previous - 당근마켓, 위메프, Oracle Korea ACS / Fedora Kor UserGroup 운영중
Database 외에도 NoSQL , Linux , Python, Cloud, Http/PHP CGI 등에도 관심이 있습니다
purityboy83@gmail.com / admin@hoing.io