기존 RDBMS와 MongoDB의 차이점 중에서
실제 사용자 입장에서 가장 크게 실감하는 부분은 아무래도 쿼리작성일 것이다.
프로그램 언어로 MongoDB 쿼리를 작성하는 것은 쿼리 빌더와 같은
래퍼( Wrapper ) 클래스의 도움을 받으면 되기 때문에 그다지 어렵지 않다.
하지만 BSON( Binary Json ) 쿼리를 직접 손으로 입력해야 하는 경우( Mongo 셰리아 쿼리 도구 사용 )에는
괄호 쌍을 맞추는 것이 여간 귀찮은 일이 아닐 수 없다.
이런 어려움을 해결하기 위해서 SQL을 MongoDB BSON 쿼리로 변환해 주는 웹 사이트들이 있지만.
상당히 부족한 상태다.
지금은 BSON 쿼리를 자주 접하면서 문법에 익숙해 지는 것밖에 방법이 없을것으로 보인다.
MongoDB의 BSON 쿼리는 기존 RDBMS의 SQL 문법과 오퍼레이터만 다를 뿐,
실제 RDBMS의 SQL이 제공하던 대부분의 기능을 제공하고 있다.
일반적으로 RDBMS의 SQL을 대체하는 MongoDB의 BSON 쿼리는 다음과 같다.
SQL | MongDB BSON 쿼리 |
INSERT | db.collection.insertOne( ) db.collection.insertMany( ) |
Batched DML (배치, INSERT, UPDATE, DELETE) |
db.collection.bulkWrite( ) |
UPDATE REPLACE (INSERT ... ON DUPLICATE KEY UPDATE ... ) |
db.collection.update( ) db.collection.update( { }, { $set:{ } }, { upsert : true } |
DELETE | db.collection.remove( ) |
SELECT | db.collection.find( ) |
SELECT ... GROUP BY ... | db.collection.MapReduce( ) db.collection.aggregate( ) |
만약 MongoDB를 처음 사용한다면 MongDB 쿼리 사용법에 대한 기본적인 내용은
MongDB의 메뉴얼( https://docs.mongodb.com/manual/crud/ )을 을 먼저 간단히 살펴볼 것을 권장한다.
그리고 이미 SQL에 익숙한 경우 SQL과 MongDB의 BSON 쿼리를 살펴보면 쉽게 익숙해질 수 있을 것이다.
MongoDB 서버를 설치해서 쿼리를 확인하는 것도 좋지만,
적절히 테스트해볼 수 있는 서버가 없는 상태레서 MongDB의 쿼리를 확인해 보고자 한다면
MongoDB Web Shell( https://mws.mongodb.com/ )을 이용해보는 것도 좋다.
1. 쿼리작성
MongoDB의 메뉴얼을 보면 Mongo 셸에서 사용할 수 있는
INSERT나 UPDATE 그리고 DELETE의 매우 다양한 변형들이 소개돼 있는 것을 확인할 수 있다.
INSERT만 해도 db.collection.insert( )와 db.collection.save( )
그리고 MongoDB 3.4로 오면서 db.collection.insertOne( )과 db.collection.insertMany( )라는 명령까지
추가된 것을 확인 할 수 있다.
하지만 이들을 모두 기억할 필요는 없다.
대부분의 명령의 사용법을 익히면 나머지 명령도 쉽게 익숙해 질 수 있다.
프로그래밍 언어를 이용해서 데이터를 핸들링하는 경우( 언어별로 드라이버가 지원하는 API의 차이는 있겠지만 )에는
insert( ) 함수나 update( ) 그리고 delete( ) 혹은 remove( ) 함수는 기본적으로 하나만 제공하고 있다.
그래서 다양한 함수를 많이 알면 좋겠지만, 복잡하다면 각 INSERT와 UPDATE 그리고 DELETE의 원형만 기억해 두어도
실제 운영이나 개발에서는 그다지 불편함이 없을 것이다.
1. INSERT
INSERT 명령은 일반적으로 두 개의 명령 인자를 사용하는데, 첫 번째 인자는 저장하고자 하는 도큐먼트다.
이때 도큐먼트는 저장하고자 하는 단일 도큐먼트일 수도 있지만,
저장하고자 하는 여러 도큐먼트의 배열일 수도 있다.
다음은 하나의 도큐먼트를 저장하는 예제다.
mongo> db.uers.insert( { name : "matt" } )
WriteResult( { "nInserted" : 1 } )
mongo> db.users.find()
{ "_id" : ObjectId("5f8b9dcd856737ab766f96e9"), "name" : "matt" }
다음 예제는 여러 도큐먼트를 한번에 저장하는 예제다.
insert()의 인자는 두 개의 원소( 도큐먼트를 가지는 배열이라서 이 예제는 두 개의 도큐먼트를 저장하게 된다. )
mongo> db.users.insert(
[
{ name : "matt" }, { name : "lara" }
]
)
BulkWriteResult({
"writeErrors" : [ ]
, "writeConcernErrors" : [ ]
, "nInserted" : 2
, "nUpserted" : 0
, "nMatched" : 0
, "nModified" : 0
, "nRemoved" : 0
, "upserted" : [ ]
})
mongo> db.users.find()
{ "_id" : ObjectId("5f8b9e79856737ab766f96ea"), "name" : "matt" }
{ "_id" : ObjectId("5f8b9e79856737ab766f96eb"), "name" : "lara" }
여기에서 db.user.insert( )의 인자로 사용된 '[ { name : " matt" }, { name : "lara" } ]는 2가지로 해석될 수 있다.
① name이라는 필드를 가지는 도큐먼트 2개
② 2개의 서브 도큐먼트를 가지는 도큐먼트 1개
이 예제에서 MongoDB 서버는 [ { name : "matt" }, { name : "lara" } ]를 ①번으로 해석했고,
그래서 2개의 도큐먼트를 users 컬렉션에 저장했다.
왜 MongoDB 서버는 2번으로 해석하지 않았을까? 그리고 MongoDB에서 이 예제가 2번으로 해석될 수 있는 것일까?
MongoDB 셸이 JSON 포맷을 사용해서 MongoDB 서버와 통신( 내부적으로는 JSON을 BSON으로 변환 )하지만,
그렇다고 해서 JSON의 모든 포맷을 허용하는 것은 아니다.
MongoDB 서버는 최상위 레벨 배열을 허용하지 않기 때문에
MonogDB 서버는 '[ { name : "matt" }, { name : "lara" } ]'를 ①번( name 필드를 가지는 2개의 도큐먼트 )으로 해석한다.
※ MongoDB 서버에서 허용하지 않는 대표적인 JSON 제한사항 참고
그리고 INSERT 명령의 두 번째 인자에는 INSERT를 처리할 때 사용할 여러 가지 옵션을 명시할 수 있다.
INSERT 명령의 두번째 인자에 사용할 수 있는 옵션으로는 다음과 같이 2가지가 있다.
또한 두번째 인자는 모두 필수가 아니기 때문에 두번째 옵션을 생략하고 디폴트 모드로 INSERT를 실행할 수도 있다.
writeConcern
INSERT 명령이 어떤 조건에서 "완료" 응답을 반환할지 결정할 수 있도록 writeConcern을 설정한다. 이때 writeConcern 필드는 도큐먼트 포맷으로 명시해야 한다.
ordered
db.collection.insert( ) 명령의 첫 번째 인자가 도큐먼트의 배열일 때, MongoDB 서버는 INSERT 명령을 배치( WriteBatch ) 명령으로 처리한다. ordered 필드는 true 또는 false의 불리언 값으로 설정하는데, ordered 필드의 값을 true로 설정하면 MongoDB 서버는 배열에 나열된 도큐먼트를 순서대로 저장하기 위해 단일 쓰레드로 하나씩 INSERT를 실행한다. ordered 필드의 값을 false로 설정하면 MonogDB 서버는 도큐먼트 배열을 멀티 쓰레드로 배분애서 동시에 INSERT를 실행한다. 별도로 옵션을 설정하지 않으면 ordered 옵션의 기본값은 true이며, 단일 쓰레드로 INSERT된다.
다음은 INSERT 명령에서 writeConcern이나 ordered 옵션을 사용한 예제다.
mongo> db.users.insert(
{ name : "lara", score : "90" }
, { writeConcern : { w : 1, j : true } }
)
WriteResult( { "nInserted" : 1 } )
mongo> db.users.insert(
[
{ name : "lara", score : "90" }
, { name : "matt" }
]
, { ordered : false }
)
BulkWriteResult({
"writeErrors" : [ ]
, "writeConcernErrors" : [ ]
, "nInserted" : 2
, "nUpserted" : 0
, "nMatched" : 0
, "nModified" : 0
, "nRemoved" : 0
, "upserted" : [ ]
})
아마도 ordered 옵션은 디폴트로 false를 사용해야 하는 게 아닐까 생각할 수도 있다.
하지만 INSERT 명령에 ordered 옵션을 false로 설정하면
실제 컬렉션에 도큐먼트가 저장되는 순서가 사용자가 명시한 순서와 다르게 저장되므로
중간에 INSERT에 실패했을 때 재처리를 어렵게 만들거나 불필요한 작업을 하게 될 수도 있다.
하지만 ordered 옵션을 true로 설정하면 사용자가 INSERT 명령에 명시한 순서대로 단일 쓰레드에 의해서
도큐먼트가 저장되므로 에러가 발생한 지점부터 INSERT가 멈추게 된다.
다음 예제는 ordered 옵션이 true이고, 여러 도큐먼트를 INSERT하는 작업에서 유니크 인덱스의 중복에러를 만났을 때
어떤 메시지가 출력되고 컬렉션의 데이터는 어떤 상태가 되는지 보여주고 있다.
mongo> db.users.createIndex( { name : 1 }, { unique : true } );
{
"numIndexesBefore" : 2,
"numIndexesAfter" : 2,
"note" : "all indexes already exist",
"ok" : 1
}
mongo> db.users.insert( {name : "matt" } );
WriteResult({ "nInserted" : 1 })
mongo> db.users.insert(
[
{ name : "lara" }, { name : "matt" }, { name : "kimberly" }
], { ordered : true }
);
BulkWriteResult({
"writeErrors" : [
{
"index" : 1,
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: testDB.users index: name_1 dup key: { name: \"matt\" }",
"op" : {
"_id" : ObjectId("5f8bb2db856737ab766f96f3"),
"name" : "matt"
}
}
],
"writeConcernErrors" : [ ],
"nInserted" : 1,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
mongo> db.users.find();
{ "_id" : ObjectId("5f8bb2cc856737ab766f96f1"), "name" : "matt" }
{ "_id" : ObjectId("5f8bb2db856737ab766f96f2"), "name" : "lara" }
하지만 같은 테스트를 ordered 옵션을 변경해서 확인해 보자.
ordered 옵션을 false로 설정하면 여러 도큐먼트가 멀티 쓰레드로 분산돼서 처리되는데,
그중에서 에러가 발생한 경우는 무시하고 나머지 도큐먼트를 INSERT 한다.
만약 배열의 하나라도 INSERT에 실패했을 경우
즉시 작업을 취소해야 한다면 ordered 옵션을 true로 설정해서 INSERT를 수행하는 것이 좋다.
mongo> db.users.createIndex( { name : 1 }, { unique : true } );
{
"numIndexesBefore" : 2
, "numIndexesAfter" : 2
, "note" : "all indexes already exist"
, "ok" : 1
}
mongo> db.users.insert( {name : "matt" } );
WriteResult({ "nInserted" : 1 })
mongo> db.users.insert(
[
{ name : "lara" }, { name : "matt" }, { name : "kimberly" }
], { ordered : false }
);
BulkWriteResult({
"writeErrors" : [
{
"index" : 1,
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: testDB.users index: name_1 dup key: { name: \"matt\" }",
"op" : {
"_id" : ObjectId("5f8bb4c2cd2e7df0b3ae9f92"),
"name" : "matt"
}
}
],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
mongo> db.users.find();
{ "_id" : ObjectId("5f8bb49f856737ab766f96f8"), "name" : "matt" }
{ "_id" : ObjectId("5f8bb4c2cd2e7df0b3ae9f91"), "name" : "lara" }
{ "_id" : ObjectId("5f8bb4c2cd2e7df0b3ae9f93"), "name" : "kimberly" }
물론 INSERT할 때 조그마한 실패는 무시하고 최대한 빨리 데이터를 저장하고자 한다면
ordered 옵션을 false로 설정해서 여러 쓰데르를 사용해 INSERT 하는 것이 좋다.
1.1.1 INSERT 도큐먼트의 ObjectId 조회
많은 RDBMS 서버가 AUTO_INCREMENT 또는 시퀀스와 같이 자동으로 아이디 값이 증가되는 기능을 제공하고 있다.
값의 포맷은 조금 다르긴 하지만 MongoDB 서버도 12Byte로 구성된
ObjectId라는 AUTO_INCREMENT 아이디 값을 제공하고 있다.
물론 MongoDB 서버에서는 샤딩 환경을 고려해야 하므로
유일한 값을 구성하는 자체가 기존 RDBMS와는 조금 다르긴 하지만,
기본적인 용도는 다른 RDBMS와 동일하게 사용할 수 있다.
다른 RDBMS에서는 이렇게 AUTO_INCREMENT 또는 시퀀스를 이용해서 INSERT를 실행한 다음,
INSERT된 값을 확인할 수 있는 기능을 제공하고 있다.
MongoDB 서버에서도 동일한 방식은 아니지만, 다음과 같이 프로그램 언어별로 MongoDB 드라이버에서
미리 "_id" 필드에에 ObjectId를 생성해서 할당하는 방식으로 INSERT될 _id 필드의 값을 미리 확인할 수 있다.
mongo> let newId = new ObjectId();
mongo> print( newId );
ObjectId("5f8bb752cd2e7df0b3ae9f94")
mongo> db.users.insert( { _id : newId, name : "matt" } );
WriteResult( { "nInserted" : 1 } )
mongo> db.users.find();
{ "_id" : ObjectId("5f8bb752cd2e7df0b3ae9f94"), "name" : "matt" }
이 예제에서 _id 필드의 값은 실제 MongoDB 서버가 할당한 값이 아니라 MongoDB 드라이버에서 할당한 값이지만,
이 값을 MongoDB 서버가 값 자체를 변경하는 것은 아니기 때문에 아무런 차이가 없다.
실제 MongoDB 서버에서 유니크한 시퀀스 값을 발급해주는 기능은 없다.
그리고 MongoDB의 ObjectId는 어느 서버에서 생성하든지 유일성이 보장되는 구조라서
굳이 단일 서버에서 발급해야 하는 제약 사항이 없으며 기존 RDBMS보다 더 유연하게 사용할 수 있다.
1.2 UPDATE
UPDATE 명령에 대해서도 Mongo 셸에서는 다음과 같이 4가지 형태의 명령을 제공한다.
하지만 대부분 사용법이 비슷하므로 대표적으로 db.collection.update( ) 명령을 자세히 살펴보겠다.
① db.collection.update( )
② db.collection.updateOne( )
③ db.collection.updateMany( )
④ db.collection.replaceOne( )
UPDATE 명령은 INSERT와는 달리 3개의 인자를 사용하는데,
세 번째 인자는 선택 옵션이라서 "업데이트 대상 도큐먼트 검색 조건"과 "업데이트 내용"만 사용할 수도 있다.
mongo> db.collection.update(
{ name : "natt" } // 업데이트 대상 도큐먼트 검색 조건
, { $set : { score : 100 } } // 업데이트 내용
, { upsert : true } // 도큐먼트 업데이트 옵션
)
UPDATE 명령의 첫 번째 인자에는 변경할 도큐먼트를 검색하는 조건을 사용한다.
UPDATE 명령은 먼저 변경할 도큐먼트를 검색해야 하므로
UPDATE 조건은 반드시 인덱스를 사용할 수 있도록 구현하는 것이 좋다.
그리고 두 번째 인자는 변경할 내용을 기술하는데,
"$set" 옵션이 없으면 기존 도큐먼트를 통째로 덮어써 버리기 때문에 주의해야 한다.
다음 예제에서는 UPDATE 명령에서 "$set"을 사용하지 않았을 때,
기존 도큐먼트의 모든 필드를 삭제하고 새로운 필드( secore : 100 )만 저장한다는 것을 확인 할 수 있다.
mongo> db.users.insert( { name : "matt", score : 90 } );
WriteResult( { "nInserted" : 1 } )
mongo> db.users.find();
{ "_id" : ObjectId("5f8bba56cd2e7df0b3ae9f95"), "name" : "matt", "score" : 90 }
mongo> db.users.update( { name : "matt" }, { score : 100 } );
WriteResult( { "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 } )
// 다시 결과를 검색해보면, name 필드는 없어지고 score 필드만 남아있게 된다.
mongo> db.users.find();
{ "_id" : ObjectId("5f8bba56cd2e7df0b3ae9f95"), "score" : 100 }
UPDATE 명령의 두 번째 인자로는 "$set"뿐만 아니라 "$currentDate"등의 오퍼레이터( 연산자 )를 이용해서
RDBMS의 타임스탬프 컬럼처럼 업데이트 하는것도 가능하다.
mongo> db.users.insert( { name : "matt", score : "90" } );
WriteResult({ "nInserted" : 1 })
mongo> db.users.update(
{ name : "matt" }
, {
$set : {score : 100 }
, $currentDate : { lastModified : true }
}
);
WriteResult( { "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 } )
mongo> db.users.find();
{
"_id" : ObjectId("5f8bbbf6cd2e7df0b3ae9f96")
, "name" : "matt"
, "score" : 100
, "lastModified" : ISODate("2020-10-18T03:54:03.853Z")
}
UPDATE 명령의 두 번째 인자에는 다양한 오퍼레이터를 사용할 수 있는데,
간단한 예제와 함께 각 오퍼레이터의 용도를 살펴보자.
$inc
필드의 값을 주어진 값만큼 증가시켜서 저장하는데,
하나의 도큐먼트 내에서도 값의 조회와 저장은 원자적( Atomic )으로 처리된다.
mongo> db.products.update(
{ sku : "abc123" }
, { $inc : { quantity : -2, "metrics.orders" : 1 } }
);
$mul
필드의 값을 주어진 값만큼 배수로 저장하는데,
하나의 도큐먼트 내에서도 값의 조회와 저장은 원자적( Atomic )으로 처리된다.
mongo> db.products.update(
{ _id : 1 }
, { $mul : { price : 1.25 } }
);
$rename
필드의 이름을 변경한다.
예를 들어, 필드의 이름을 잘못 입력해서 다른 이름으로 변경하고자 할 때에는
다음과 같이 $rename 연산자를 사용하면 된다.
특정 도큐먼트의 필드명만 변경할 수도 있으며, 전체 도큐먼트의 필드명을 변경할 수도 있다.
하지만 전체 도큐먼트 변경은 서비스에 상당한 영향을 미칠 수 있으니 주의가 필요하다.
// _id가 1인 도큐먼트의 필드명을 변경한다.
mongo> db.products.update(
{ _id : 1 }
, { $rename : { "name" : "name" } }
);
// 전체 필드명을 변경한다.
mongo> db.products.updateMany(
{ }
, { $rename : { "name" : "name" } }
);
$setOnInsert
$setOnInsert는 $set과 동일한 방식으로 사용하지만,
upsert 옵션이 true인 UPDATE 명령이 업데이트해야 할 도큐먼트를 찾지 못해서 INSERT를 실행해야 할 때에만
$setOnInsert의 내용이 적용된다.
다음 명령에서 $setOnInsert의 defaultQty 필드의 값은 products 컬렉션에 도큐먼트를 INSERT 할 때에만 저장된다.
만약 _id 필드의 값이 1인 도큐먼트가 있다면 UPDATE가 실행되고 이때는 $setOnInsert 옵션이 무시된다.
mongo> db.products.update(
{ _id : 1 }
, {
$set : { item : "apple" }
, $setOnInsert : { defaultQty : 100 }
}
, { upsert : true }
);
$set
도큐먼트의 필드 값을 변경한다.
mongo> db.products.update(
{ _id : 1 }
, {
$set : { item : "apple" }
}
, { upsert : true }
);
$unset
도큐먼트의 필드를 삭제한다.
mongo> db.products.update(
{ sku : "unknown" }
, { $unset : { quantity : "", instock : "" } }
);
$unset 명령은 도큐먼트의 필드 자체를 삭제하는 명령이라서 $unset 옵션에 사용한 빈 문자열("")은 큰 의미를 가지지 않는다.
"" 대신 1을 사용해도 결과는 같다.
( 하지만 메뉴얼에서는 삭제할 필드에 대해서 ""를 사용하도록 가이드 하고 있으니 $unset은 위의 방식으로 사용하자. )
mongo> db.products.update(
{ sku : "unknown" }
, { $unset : { quantity : 1, instock : 1 } }
);
$currentDate
필드의 값을 현재 시각으로 변경한다.
도큐먼트의 status 필드 값을 "D"로 변경하고 lastModified 필드와 cancellation.date 필드는 현재 시각으로 설정한다.
이때 lastModified 필드는 Date 타입으로 설정하고,
cancellation.date 필드는 타임스탬프 타입으로 값을 설정하도록 하고 있다.
mongo> db.products.update(
{ _id : 1 }
, {
$currentDate : {
lastModified : true
, "cancellation.date" : { $type : "timestamp" }
}
, $set {
status : "D"
}
}
);
마지막으로 update 명열의 세 번째 인자에는 UPDATE 명령을 실행할 때 적용할 여러가지 옵션을 설정할 수 있는데,
대표적으로 다음과 같은 옵션을 사용할 수 있다.
upsert
upsert옵션은 INSERT 명령에서는 효과가 없으며, UPDATE 명령에서만 사용할 수 있다. upsert 옵션의 디폴트 값은 false이며, upsert 옵션을 false로 설정하면 UPDATE 명령이 조건에 맞는 도큐먼트를 찾지 못하더라도 UPDATE 명령이 어떤 변경도 발생시키지 않는다. 반대로 upsert 옵션을 true로 설정하면 UPDATE 명령이 조건에 맞는 도큐먼트를 찾지 못했을 때 자동으로 UPDATE할 내용으로 새로운 도큐먼트를 INSERT 한다.
multi
기본적으로 MongoDB의 UPDATE 명령은 단일 도큐먼트의 업데이트만 수행한다. 즉 검색조건에 일치하는 도큐먼트가 2개 이상이더라도 그중에서 하나만 변경된다. 그런데 UPDATE 명령에 multi 옵션을 true로 설정하면 조건에 일치하는 모든 도큐먼트를 변경한다. UPDATE 명령에서 multi 옵션의 디폴트 값은 false인데, 이는 무조건 하나의 도큐먼트만 업데이트 한다. 일반적으로 RDBMS에서 UPDATE 쿼리는 조건에 부합하는 모든 도큐먼트를 변경하는 것이 디폴트 모드인데 MongoDB 서버에선느 단 한건만 업데이트 하는 것이 디폴트 모드라는 점을 기억하자.
writeConcern
UPDATE 명령이 어떤 조건에서 "완료" 응답을 반환할지 결정할 수 있도록 WriteConcern을 설정한다. 이때 writeConcern 필드는 도큐먼트 포맷으로 명시해야 한다.
collation
UPDATE 명령이 변경할 대상 도큐먼트를 검색할 때 사용할 문자 셋과 콜레이션을 명시한다. 별도로 콜레이션을 명시하지 않으면 컬렉션에 정의된 콜레이션을 디폴트로 사용한다. 콜레이션을 명시하는 방법은 "7.2.3 문자셋과 콜레이션"을 참조하자.
다음 예제는 UPDATE 명령에서 조건에 일치하는 도큐먼트가 없을 때,
upsert 옵션의 값에 따라서 새로운 도큐먼트를 INSERT 할지 아니면 UPDATE를 무시하는지 보여주고 있다.
mongo> db.users.drop();
true
// upsert = false 모드에서는 name="matt"인 도큐먼트가 없으므로 아무런 변경도 실행되지 않음
mongo> db.users.update( { name : "matt" }, { $set : { score : 90 } }, { upsert : false } );
WriteResult( { "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 } )
// upsert = true 모드에서는 name="matt"인 도큐먼트가 없으므로 아무런 변경도 실행되지 않음
mongo> db.users.update( { name : "matt" }, { $set : { score : 90 } }, { upsert : true } );
WriteResult({
"nMatched" : 0
, "nUpserted" : 1
, "nModified" : 0
, "_id" : ObjectId("5f8bc5d1a14099a70cd94708")
})
// UPDATE 결과 INSERT된 도큐먼트 확인
mongo> db.users.find();
{ "_id" : ObjectId("5f8bc5d1a14099a70cd94708"), "name" : "matt", "score" : 90 }
// 다시 upsert = true 모드로 UPDATE 실행하면 일치하는 도큐먼트를 업데이트함
mongo> db.users.update( { name : "matt" }, { $set : { score : 100 } }, { upsert : true } );
WriteResult( { "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 } )
mongo> db.users.find();
{ "_id" : ObjectId("5f8bc5d1a14099a70cd94708"), "name" : "matt", "score" : 100 }
1.2.1 배열 필드 업데이트
MongoDB 서버에서는 배열 필드에 대한 많은 오퍼레이션을 제공하고 있다.
따라서 배열 필드의 데이터를 변경하기 위해 도큐먼트를 클라이언트로 가져온 다음
가공해서 다시 변경하는 형태로 업데이트하지 않고도 특정 위치의 배열 엘리먼트를 수정할 수 있다.
예를 들어, 다음과 같이 주문 내역을 관리하는 order_status 컬렉션을 가정해보자.
mongo> db.orders_status.insert(
{
"_id" : ObjectId("591ea3d546dc618585c479eb")
, "account_id" : 999
, "order_id" : "1"
, "items" : [
{
"item_id" : "1"
, "update_time" : ISODate("2017-08-06T06:55:09.034Z")
, "status" : "COMPLETE"
}, {
"item_id" : "2"
, "update_time" : ISODate("2017-08-06T06:55:09.034Z")
, "status" : "DELIVERY"
}, {
"item_id" : "3"
, "update_time" : ISODate("2017-08-06T06:55:09.034Z")
, "status" : "COMPLETE"
}
]
}
);
WriteResult( { "nInserted" : 1 } )
items 필드에 저장된 배열에 새로운 엘리먼트를 추가하고자 하는 경우에는
$push 명령을 이용해서 새로운 배열 엘리먼트를 쉽게 추가할 수 있다.
mongo> db.orders_status.update(
{ "_id" : ObjectId("591ea3d546dc618585c479eb") }
, {
"$push" : {
"items" : {
"item_id" : "4"
, "update_time" : ISODate("2017-08-06T06:55:09.034Z")
, "status" : "COMPLETE"
}
}
}
);
WriteResult( { "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 } )
그 뿐만 아니라 배열 앨리먼트 중에서 item_id가 "1"인 엘리먼트만 새로운 값으로 대체하는 작업도
MongoDB 서버의 배열 오퍼레이션을 이용하면 쉽게 처리할 수 있다.
다음 예제의 $set 명령에서 "items.$" 표시는 배열 엘리먼트 중에서
검색 조건( "item_id" : 1 )에 일치하는 엘리먼트를 지칭하는 예약어다.
mongo> db.orders_status.update(
{ "_id" : ObjectId("591ea3d546dc618585c479eb"), "item_id" : "2" }
, {
"$set" : {
"items.$" : {
"item_id" : "2"
, "update_time" : ISODate("2017-08-06T06:55:09.034Z")
, "status" : "COMPLETE"
}
}
}
);
// 다시
그런데 배열 타입의 값을 가지는 한가지 제약 사항은 유일성( Unique )이 보장이 되지 않는다은 것이다.
위 예제에서 itmes 필드에 저장된 item_id가 유니크해야 한다고 가정해보자.
하지만 MongoDB 배열은 배열 엘리먼트 간의 유일성을 보장해주지 않는다.
게다가 order_status 컬렉션에서 "items"배열 필드의 자식 필드인 item_id에 대해서
별도의 유니크 인덱스를 생성할 방법도 없다.
MongoDB의 유니크 인덱스는 도큐먼트 간의 중복만 방지할 수 있기 때문이다.
만약 이렇게 하나의 도큐먼트 내에서 배열 필드의 유일성을 보장해야 하는 경우에는 다음과 같이
배열을 새로운 서브 도큐먼트로 처리하면 데이터를 변경할 때 유일성을 보장받을 수도 있다.
// 배열 필드에서 itme_id 필드의 값이 1인경우
// 이를 "item_id-1"이라는 이름의 새로운 자식 필드( 서브 도큐먼트 )로 저장
db.order_status.insert(
{
"_id" : ObjectId("591ea3d546dc618585c479eb")
, "account_id" : 999
, "order_id" : "1"
, "items" : {
"item_id-1" : {
"update_time" : ISODate("2017-08-06T06:55:09.043Z")
, "status":"READY"
}
, "item_id-2" : {
"update_time" : ISODate("2017-08-06T06:55:09.034Z")
, "status":"COMPLETE"
}
}
}
);
WriteResult( { "nInserted" : 1 } )
// 배열 필드에서 item_id 필드의 값이 1인 엘리먼트를 변경하고자 할 때는
// "items.item_id-1" 필드의 서브 도큐먼트를 변경
monbo> db.order_status.update(
{ "_id" : ObjectId("591ea3d546dc618585c479eb") }
, {
$set : {
"items.item_id-1" : {
"update_time" : ISODate("2017-08-06T07:19:09.034Z")
, "status" : "COMPLETED"
}
}
}
);
WriteResult( { "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 } )
1.3 REMOVE( DELETE )
컬렉션의 도큐먼트를 삭제하는 명령으로는 다음과 같이 3가지 형태의 명령을 제공한다.
대표적으로 db.collection.remove( ) 명령과 옵션에 대해서 살펴보겠다.
① db.collection.remove( )
② db.collection.deleteOne( )
③ db.collection.deleteMany( )
REMOVE 명령은 다음과 같이 2개의 JSON 도큐먼트 인자를 필요로 한다.
하지만 두 번째 인자는 선택 옵션이므로 첫 번째 인자인 "삭제 대상 도큐먼트 검색 조건" 만으로
REMOVE 명령을 사용할 수도 있다.
mongo> db.collection.remove(
{ name : "matt" } // 삭제 대상 도큐먼트 검색 조건
, { justOne : true } // 도큐먼트 삭제 옵션
)
REMOVE 명령의 첫 번째 인자에는 삭제할 도큐먼트를 검색하는 조건을 명시한다.
REMOVE 명령도 UPDATE 명령과 같이 먼저 삭제할 도큐먼트를 검색해야 하므로
REMOVE 조건은 반드시 인덱스를 사용할 수 있도록 구현하는것이 좋다.
REMOVE 명령의 두 번째 인자로는 도큐먼트를 삭제하기 위한 옵션을 설정할 수 있는데,
대표적으로 다음과 같은 옵션을 사용할 수 있다.
jusetOne
MongoDB의 REMOVE 명령은 UPDATE 명령과는 달리 여러 도큐먼트가 삭제된느 것이 디폴트 모드다. 만약 justOne 옵션을 명시하지 않으면 기본적으로 RDBMS와 같이 조건에 일치하는 모든 도큐먼트를 삭제하는 방식으로 작동한다. 하지만 조건에 일치하는 도큐먼트만 삭제하고자 한다면 justOne 옵션을 true로 설정하면 된다.
writeConcern
Remove 명령이 어떤 조건에서 "완료" 응답을 반환할지 결정할 수 있도록 WriteConcern을 설정한다. 이때 writeConcern 필드는 도큐먼트 포맷으로 명시해야 한다.
collation
REMOVE 명령이 삭제할 대상 도큐먼트를 검색할 때 사용할 문자 셋과 콜레이션을 명시한다. 별도로 콜레이션이 명시되지 않으면 컬렉션에 정의된 콜레이션을 디폴트로 사용한다. 콜레이션을 명시하는 방법은 "7.2.3 문자셋과 콜레이션"절을 참조하자.
컬렉션의 모든 도큐먼트를 삭제하고자 할 때는 다음과 같이 조건이 없이 REMOVE 명령을 사용한다.
REMOVE 명령은 컬렉션의 도큐먼트를 하나씩 삭제하기 때문에 컬렉션의 도큐먼트 건수가 많다면 삭제 작업에 상당히 오랜 시간이 걸릴 것이다.
하지만 MongoDB는 TRUNCATE 명령을 지원하지 않는다.
그래서 현재로서는 컬렉션 자체를 삭제( db.collection.drop( ) 명령 )하고 직접 생성하는 것이 가장 빠른 방법이다.
mongo> db.users.remove( {} );
WriteResult( { "nRemved" : 121 } )
특정 조건에 일치하는 도큐먼트만 삭제하고자 할 때는 다음과 같이 REMOVE 명령의 첫 번째 인자로 조건을 도큐먼트로 입력한다.
만약 조건에 일치하는 도큐먼트 중에서 첫 번째로 검색된 도큐먼트만 삭제하고자 한다면 justOne 옵션을 사용한다.
// 조건에 일치하는 모든 도큐먼트 삭제
mongo> db.users.remove( { name : "matt" } );
WriteResult( { "nRemoved" : 2 } )
// 조건에 일치하는 도큐먼트 중에서 첫 번째 도큐먼트만 삭제
mongo> db.users.remove( { name : "matt" }, { justOne : true } );
WriteResult( { "nRemoved" : 1 } )
REMOVE 명령은 justOne 옵션의 설정값에 따라서 검색 결과에 일치하는 도큐먼트 하나 또는 전부를 삭제할 수 있다. justOne 옵션을 true로 설정하면 MongoDB 서버는 검색 조건에 일치하는 첫 번째 도큐먼트만 삭제하고, justOne 옵션을 falses로 설정하면 검색 조건에 일치하는 모든 도큐먼트를 삭제한다. 하지만 검색 조건에 일치하는 여러 도큐먼트 중에서 특정 도큐먼트만 삭제하는 것은 불가능하다. 만약 검색 조건에 일치하는 여러 도큐먼트 중에서 특정 필드를 기준으로 정렬해서 특정 도큐먼트만 삭제하고 한다면 FindAndModify 명령을 활용할 수 있다. FindAndModify 명령의 자세한 설명은 "8.1.1.7 FindModify" 절을 참조하자.
그리고 RDBMS에서는 DELETE를 LIMIT 조건과 결합해서 사용하는 경우가 많다. 하지만 MongoDB 3.6 버전까지는 조건에 일치하는 도큐먼트 중에서 일부 N건만 삭제한다거나 하는 기능은 제공하고 있지 않다. 실제 불필요한 데이터를 통째로 삭제하는 경우에는 MongoDB 서버에 과부하가 발생할 수 있는데, 서비스 쿼리가 실행 중인 MongoDB 서버에서는 이런 과도한 삭제는 문제가 될 수 있다. 이렇게 대량으로 삭제해야 할 때에는 삭제 대상을 찾아서 IN 조건이나 동등 조건으로 도큐먼트를 하나씩 삭제하는 방법밖에 없는데, 이런 방식은 Mongo 셸에서 단순히 처리하기는 조금 어려움이 있을 수 있다. 그래서 데이터를 삭제하기 위해서 프로그램을 개발해야 할 필요도 있다. 현재 LIMIT 기능을 지원하는 remove 명령에 대한 요청은 MongoDB JIRA에 올려져 있는 상태인데, 아직은 기능이 구현되지 않았다. 만약 기능의 구현 상태가 궁금하다면 아래 JIRA 사이트를 참조하도록 하자.
출처 : https://wikibook.co.kr/real-mongodb/
'MongoDB' 카테고리의 다른 글
[MongoDB] 확장 검색 쿼리 - Aggregation의 목적 및 작동방식 (1) | 2020.11.15 |
---|---|
[MongoDB] 보조적인 JOIN 기능 - $lookup (2) | 2020.11.03 |
[MongoDB] CRUD 쿼리사용 - BulkWrite (0) | 2020.10.20 |
[MongoDB] 격리된( $isolated ) UPDATE와 REMOVE (0) | 2020.10.20 |
[MongoDB] MongoDB 설치하기 - Windows (0) | 2020.10.16 |