[MongoDB] 도큐먼트 갱신
- Database(데이터베이스)/NOSQL - MongoDB
- 2021. 11. 3. 18:32
참조
- 몽고DB 완벽가이드 3판
1.1 도큐먼트 치환
- replaceOne은 도큐먼트를 새로운 것으로 완전치 치환해주는 함수입니다.
- 이는 대대적인 스키마 마이그레이션에 유용합니다.
- 예를 들어 사용자 도큐먼트를 다음과 같이 큰 규모로 변경한다고 가정합니다.
use('jbh_MongoDB');
db.users.insertOne({"name" : "joe", "friends" : 32, "enemies" : 2});
{
"_id": {
"$oid": "6180fd8ec9fdfd8cd0f7d657"
},
"name": "joe",
"friends": 32,
"enemies": 2
}
- "friends" 와 "enemies" 필드를 "relationships" 라는 서브도큐먼트로 옮겨 보겠습니다.
use('jbh_MongoDB');
var joe = db.users.findOne({"name" : "joe"});
joe.relationships = {"friends" : joe.friends, "enemies" : joe.enemies};
joe.username = joe.name;
delete joe.friends;
delete joe.enemies;
delete joe.name;
db.users.replaceOne({"name" : "joe"}, joe);
{
"_id": {
"$oid": "6180fd8ec9fdfd8cd0f7d657"
},
"relationships": {
"friends": 32,
"enemies": 2
},
"username": "joe"
}
- 흔히 하는 실수로, 조건절로 2개 이상의 도큐먼트가 일치되게 한 후 두 번째 매개변수로 중복된 "_id" 값을 갖는 도큐먼트를 생성하는 경우가 있습니다.
- 이때 데이터베이스는 오류를 반환하고 아무것도 변경하지 않습니다.
use('jbh_MongoDB');
db.users.drop();
db.people.insertOne({"name" : "joe", "age" : 65});
db.people.insertOne({"name" : "joe", "age" : 20});
db.people.insertOne({"name" : "joe", "age" : 49});
joe = db.people.findOne({"name" : "joe", "age" : 20});
joe.age++;
db.people.replaceOne({"name" : "joe"}, joe);
After applying the update, the (immutable) field '_id' was found to have been altered to _id: ObjectId('6180ffea2ae980e6008e8697')
- 위와 같이 갱신을 요청하면 데이터베이스는 {"name" : "joe"} 와 일치하는 도큐먼트를 찾습니다.
- 첫 번째로 65세 joe 를 발견하고 치환하려는 도큐먼트와 같은 "_id" 를 갖는 도큐먼트가 이미 컬렉션에 있음에도 현재 joe 변수 내 도큐먼트를 치환하려고 시도합니다.
- 그런데 "_id" 값은 고유해야 하므로 갱신에 실패하게 됩니다.
- 위와 같은 상황을 피하려면 "_id" 키로 일치하는 고유한 도큐먼트를 찾는 방법처럼, 고유한 도큐먼트를 갱신 대상으로 지정하는 것이 좋습니다.
use('jbh_MongoDB');
db.users.drop();
db.people.insertOne({"name" : "joe", "age" : 65});
db.people.insertOne({"name" : "joe", "age" : 20});
db.people.insertOne({"name" : "joe", "age" : 49});
joe = db.people.findOne({"name" : "joe", "age" : 20});
joe.age++;
db.people.replaceOne({"_id" : ObjectId("6180ffea2ae980e6008e8697")}, joe);
{
"acknowledged": true,
"insertedId": null,
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
{
"_id": {
"$oid": "6180ffea2ae980e6008e8697"
},
"name": "joe",
"age": 21
}
1.2 갱신 연산자
- 일반적으로 도큐먼트의 특정 부분만 갱신하는 경우가 많습니다.
- 부분 갱신에는 원자적 갱신 연산자를 사용합니다.
- 갱신 연산자는 키를 변경, 추가, 제거하고, 심지어 배열과 내장 도큐먼트를 조작하는 복잡한 갱신 연산을 지정하는 데 사용하는 특수키 입니다.
- 아래 예제는, 누군가 페이지를 방문할 때마다 카운터가 증가하는 할 예제 입니다.
use('jbh_MongoDB');
db.people.drop();
db.analytics.insertOne({"url" : "www.example.com", "pageviews" : 52});
{
"_id": {
"$oid": "618101cd9c9804fd1d2cfb30"
},
"url": "www.example.com",
"pageviews": 52
}
- 누군가 페이지를 방문할 때마다 URL로 페이지를 찾고 "pageviews" 키의 값을 증가시키려면
$inc
제한자를 사용합니다.
use('jbh_MongoDB');
db.people.drop();
db.analytics.insertOne({"url" : "www.example.com", "pageviews" : 52});
db.analytics.updateOne({"url" : "www.example.com"},
{"$inc" : {"pageviews" : 1}});
{
"acknowledged": true,
"insertedId": null,
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
{
"_id": {
"$oid": "618101cd9c9804fd1d2cfb30"
},
"url": "www.example.com",
"pageviews": 53
}
- 연산자를 사용할 때 "_id" 값은 변경할 수 없습니다. (변경하려면 도큐먼트 전체를 치환해야 합니다.)
- 그외 다른 키 값은 모두 변경할 수 있습니다.
- "$set" 은 필드 값을 설정합니다.
- 필드가 존재하지 않으면 새 필드가 생성됩니다.
- 이 기능은 스키마를 갱신하거나 사용자 정의 키를 추가할 때 편리합니다.
use('jbh_MongoDB');
db.analytics.drop();
db.users.insertOne({"name" : "joe", "age" : 30 ,"성별" : "남자", "위치" : "수원"});
{
"_id": {
"$oid": "618103bec9b11bf69ecf491a"
},
"name": "joe",
"age": 30,
"성별": "남자",
"위치": "수원"
}
- 위의 예제에서 사용자가 좋아하는 책을 프로필에 추가하려면
"$set"
을 사용하면 됩니다.
use('jbh_MongoDB');
db.users.updateOne({"_id" : ObjectId("6181041dde032de2a7ac0208")},
{"$set" : {"favorite book" : "해리포터"}});
{
"acknowledged": true,
"insertedId": null,
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
{
"_id": {
"$oid": "6181041dde032de2a7ac0208"
},
"name": "joe",
"age": 30,
"성별": "남자",
"위치": "수원",
"favorite book": "해리포터"
}
- 사용자가 다른 책을 좋아한다고 하면 "$set" 으로 값을 변경할 수도 있습니다.
use('jbh_MongoDB');
db.users.updateOne({"name" : "joe"},
{"$set" : {"favorite book" : "반지의 제왕"}});
{
"acknowledged": true,
"insertedId": null,
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
{
"_id": {
"$oid": "6181041dde032de2a7ac0208"
},
"name": "joe",
"age": 30,
"성별": "남자",
"위치": "수원",
"favorite book": "반지의 제왕"
}
- 사용자가 책을 좋아하지 않는다는 것을 깨닫게 되면 "$unset" 으로 키와 값을 모두 제거할 수 있습니다.
use('jbh_MongoDB');
db.users.updateOne({"name" : "joe"},
{"$unset" : {"favorite book" : "반지의 제왕"}});
{
"acknowledged": true,
"insertedId": null,
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
{
"_id": {
"$oid": "6181041dde032de2a7ac0208"
},
"name": "joe",
"age": 30,
"성별": "남자",
"위치": "수원"
}
- 키를 추가, 변경, 삭제할 때는 항상 $ 제한자를 사용해야 합니다.
- 하지만, 초보자가 흔히 범하는 오류가 있습니다.
- 키 값을 다른 값으로 갱신하게 되면 오류가 발생합니다.
use('jbh_MongoDB');
db.users.updateOne({"name" : "joe"},
{"name" : "Kim"});
Update document requires atomic operators
use('jbh_MongoDB');
db.users.updateOne({"name" : "joe"},
{"$set" : {"name" : "Kim"}});
{
"_id": {
"$oid": "6181041dde032de2a7ac0208"
},
"name": "Kim",
"age": 30,
"성별": "남자",
"위치": "수원"
}
- 이처럼 갱신 도큐먼트는 갱신 연산자를 반드시 포함해서 사용해야 합니다.
- "$inc" 연산자는 이미 존재하는 키의 값을 변경하거나 새 키를 생성하는데 사용합니다.
- 분석, 분위기, 투표 등과 같이 자주 변하는 수치 값을 갱신하는데 매우 유용합니다.
- 아래 예제는 게임을 저장하고 점수를 갱신하는 게임 컬렉션을 생성한다고 가정합니다.
- 사용자가 핀볼이라는 게임을 시작하면, 게임 이름과 플레이어로 게임을 식별하는 도큐먼트를 하나 삽입합니다.
use('jbh_MongoDB');
db.users.drop();
db.games.insertOne({"game" : "pinball", "user" : "joe"});
- 게임에서는 공이 범퍼에 부딪치면 플레이어의 점수가 증가합니다.
- 점수는 꽤 쉽게 얻을 수 있고 기본 단위가 50이라고 가정합니다.
- 플레이어의 점수에 50을 더하려면 "$inc" 제한자를 사용하면 됩니다.
use('jbh_MongoDB');
db.games.updateOne({"game" : "pinball", "user" : "joe"},
{"$inc" : {"score" : 50}});
{
"acknowledged": true,
"insertedId": null,
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
{
"_id": {
"$oid": "618108247bd04bf7957cae86"
},
"game": "pinball",
"user": "joe",
"score": 50
}
- 처음에 "score" 키는 존재하지 않았지만, "$inc" 제한자에 의해 생성되고 50만큼 증가했습니다.
- 공이
보너스
슬롯에 들어가면 10000점을 추가한다고 가정합니다.
use('jbh_MongoDB');
db.games.updateOne({"game" : "pinball", "user" : "joe"},
{"$inc" : {"score" : 10000}});
{
"_id": {
"$oid": "618108247bd04bf7957cae86"
},
"game": "pinball",
"user": "joe",
"score": 10050
}
- 기존에 50에서 10000을 더한 10050 으로 값이 증가한 것을 확인할 수 있습니다.
- "$inc" 는 int, long, double, decimal 타입 값에만 사용할 수 있습니다.
- null, 불리언, 문자열로 나타낸 숫자와 같이 여러 언어에서 숫자로 자동 변환되는 데이터형의 값에는 사용할 수 없습니다.
use('jbh_MongoDB');
db.games.drop();
db.strcounts.insert({"count" : "1"})
db.strcounts.update({}, {"$inc" : {"count" : 1}});
Cannot apply $inc to a value of non-numeric type. {_id: ObjectId('61810b4ee3b253fbfcd79ecb')} has the field 'count' of non-numeric type string
- 배열을 다루는 데 갱신 연산자를 사용할 수 있습니다.
- "$push" 는 배열이 이미 존재하면 배열 끝에 요소를 추가하고, 존재하지 않으면 새로운 배열을 생성합니다.
- 예를들어, 블로그 게시물에 배열 형태의 "comments" 키를 삽입한다고 가정합니다.
- 존재하지 않던 "comments" 배열이 생성되고 댓글이 생성되는 것을 볼 수 있습니다.
use('jbh_MongoDB');
db.strcounts.drop();
db.blog.posts.insertOne({"title" : "A blog post", "content" : "..."});
db.blog.posts.updateOne({"title" : "A blog post"},
{"$push" : {"comments" :
{"name" : "joe", "email" : "joe@example.com", "content" : "nice post."}}});
db.blog.posts.findOne();
{
"_id": {
"$oid": "61810d3efc787aa16eb34fb9"
},
"title": "A blog post",
"content": "...",
"comments": [
{
"name": "joe",
"email": "joe@example.com",
"content": "nice post."
}
]
}
- 댓글을 더 추가하려면 "$push" 를 또 사용하면 됩니다.
use('jbh_MongoDB');
db.strcounts.drop();
db.blog.posts.insertOne({"title" : "A blog post", "content" : "..."});
db.blog.posts.updateOne({"title" : "A blog post"},
{"$push" : {"comments" :
{"name" : "joe", "email" : "joe@example.com", "content" : "nice post."}}});
db.blog.posts.updateOne({"title" : "A blog post"},
{"$push" : {"comments" :
{"name" : "bob", "email" : "bob@example.com", "content" : "good post."}}});
db.blog.posts.findOne();
{
"_id": {
"$oid": "61810d3efc787aa16eb34fb9"
},
"title": "A blog post",
"content": "...",
"comments": [
{
"name": "joe",
"email": "joe@example.com",
"content": "nice post."
},
{
"name": "bob",
"email": "bob@example.com",
"content": "good post."
}
]
}
- 더 복잡한 개병 기능에도 사용할 수 있습니다.
- "$push", "$each" 제한자를 사용하면 작업 한번으로 값을 여러 개 추가할 수 있습니다.
use('jbh_MongoDB');
db.stock.ticker.insertOne({"_id" : "GOOG"});
db.stock.ticker.updateOne({"_id" : "GOOG"},
{"$push" : {"hourly" : {"$each" : [562.776, 562.790, 559.123]}}});
{
"_id": "GOOG",
"hourly": [
562.776,
562.79,
559.123
]
}
- 배열을 특정 길이로 늘이려면 "$slice" 를 "$push" 와 결합해 사용합니다.
- 배열이 특정 크기 이상으로 늘어나지 않게 하고 효과적으로
top N
목록을 만들 수 있습니다.
use('jbh_MongoDB');
db.movies.insertOne({"title" : "horror"});
db.movies.updateOne({"title" : "horror"},
{"$push" : {"top10" : {"$each" : ["쏘우1", "쏘우2", "쏘우3","쏘우4","쏘우5","쏘우6","쏘우7"
,"쏘우8","쏘우9","쏘우10","쏘우11","쏘우12",],
"$slice" : -10}}});
{
"_id": {
"$oid": "61810ff742a25ddae5f16ca1"
},
"title": "horror",
"top10": [
"쏘우3",
"쏘우4",
"쏘우5",
"쏘우6",
"쏘우7",
"쏘우8",
"쏘우9",
"쏘우10",
"쏘우11",
"쏘우12"
]
}
- 위의 예제는 배열에 추가할 수 있는 요소의 개수를 10개로 제한합니다.
- 추가 후에 배열 요소의 개수가 10보다 작으면 모든 요소가 유지되고, 10보다 크면 마지막 10개 요소만 유지됩니다.
- "$sort" 제한자를 "$push" 작업에 적용할 수 있습니다.
use('jbh_MongoDB');
db.movies.drop();
db.movies.insertOne({"title" : "horror"});
db.movies.updateOne({"title" : "horror"},
{"$push" : {"top10" : {"$each" : [{"name" : "나이트메어", "rating" : 6.6},
{"name" : "Saw" , "rating" : 4.3},
{"name" : "태극기 휘날리며" , "rating" : 3.3}],
"$slice" : -10,
"$sort" : {"rating" : 1}}}});
{
"_id": {
"$oid": "618111caa5abfdac3927a342"
},
"title": "horror",
"top10": [
{
"name": "태극기 휘날리며",
"rating": 3.3
},
{
"name": "Saw",
"rating": 4.3
},
{
"name": "나이트메어",
"rating": 6.6
}
]
}
- "$slice" 나 "$sort" 를 배열상에서 "$push" 와 함께 쓰려면 반드시 "$each" 도 사용해야 합니다.
- 특정 값이 배열에 존재하지 않을 때 해당 값을 추가하면서, 배열을 집합처럼 처리하려면 쿼리 도큐먼트에 "$ne" 를 사용합니다.
- 예를 들어 인용 목록에 저자가 존재하지 않을 때만 해당 저자를 추가하려면 다음처럼 하면 됩니다.
use('jbh_MongoDB');
db.papers.drop();
db.papers.insertOne({"name" : "Joe"});
db.papers.updateOne({"authors cited" : {"$ne" : "Richie"}},
{$push : {"authors cited" : "Richie"}});
db.papers.findOne();
{
"_id": {
"$oid": "61811b118680ba324b61b7bf"
},
"name": "Joe",
"authors cited": [
"Richie"
]
}
- "$addToSet" 을 이용할 수도 있습니다.
- "$addToSet" 은 "$ne" 가 작동하지 않을 때나 "$addToSet"을 사용하면 무슨 일이 일어났는지 더 잘 알 수 있을 때 유용합니다.
- 예를 들어 사용자 정보 도큐먼트가 있다고 가정하고, 도큐먼트에는 사용자가 입력한 이메일 주소 셋이 있습니다.
use('jbh_MongoDB');
db.users.insertOne({"username" : "joe", "emails" : ["joe@example.com", "joe@gmail.com", "joe@yahoo.com"]});
db.users.findOne({"_id" : ObjectId("61811d11f4ca6ac8b8453c0c")})
{
"_id": {
"$oid": "61811d11f4ca6ac8b8453c0c"
},
"username": "joe",
"emails": [
"joe@example.com",
"joe@gmail.com",
"joe@yahoo.com"
]
}
- 위와 같이 주소들이 있는데, 다른 주소를 추가할 때 "$addToSet" 을 사용하면 중복을 피할 수 있습니다.
use('jbh_MongoDB');
db.users.updateOne({"_id" : ObjectId("61811d11f4ca6ac8b8453c0c")},
{"$addToSet" : {"emails" : "joe@gmail.com"}});
db.users.findOne({"_id" : ObjectId("61811d11f4ca6ac8b8453c0c")});
db.users.updateOne({"_id" : ObjectId("61811d11f4ca6ac8b8453c0c")},
{"$addToSet" : {"emails" : "joe@daum.com"}});
db.users.findOne({"_id" : ObjectId("61811d11f4ca6ac8b8453c0c")});
{
"_id": {
"$oid": "61811d11f4ca6ac8b8453c0c"
},
"username": "joe",
"emails": [
"joe@example.com",
"joe@gmail.com",
"joe@yahoo.com",
"joe@daum.com"
]
}
- 기존에
joe@gmail.com
은 이미 존재하기 때문에 갱신되지 않고,joe@daum.com
은 없는 주소이기 때문에 추가된 것을 확인할 수 있습니다.
- 배열에서 요소를 제거하는 방법에는 몇 가지가 있습니다.
- 배열을 큐나 스택처럼 사용하려면 배열의 양쪽 끝에서 요소를 제거하는 "$pop" 을 사용하면 됩니다.
- {"$pop" : {"key" : 1}} 은 배열의 마지막부터 요소를 제거하고 {"$pop" : {"key" : -1}} 은 배열의 처음부터 요소를 제거합니다.
- "$pull" 은 주어진 조건에 맞는 배열 요소를 제거하는데 사용합니다.
use('jbh_MongoDB');
db.lists.insertOne({"todo" : ["dishes", "laundry", "dry cleaning"]});
db.lists.updateOne({}, {"$pull" : {"todo" : "laundry"}});
db.lists.findOne();
{
"_id": {
"$oid": "61824ed1ebc4a175d63211f0"
},
"todo": [
"dishes",
"dry cleaning"
]
}
- "$pull" 은 조건와 일치하는 모든 도큐먼트를 지웁니다.
- 예를 들어 [1, 1, 2, 1] 과 같은 배열에서 1을 뽑아내면 [2] 하나만 남게 됩니다.
- 값이 여러 개인 배열에서 일부를 변경하는 조작은 꽤 어렵습니다.
- 배열 내 여러 값을 다루는 방법은 두 가지가 있습니다.
- 위치를 이용하거나, 위치 연산자를 사용할 수 있습니다.
- 배열 인덱스틑 기준이 0이며, 배열 요소는 인덱스를 도큐먼트의 키 처럼 사용합니다.
use('jbh_MongoDB');
db.blog.drop();
db.blog.posts.drop();
db.blog.insertOne({"content" : "...", "comments" : [{"comment" : "good post", "author" : "John", "votes" : 0},
{"comment" : "i thought it was too short", "author" : "Claire", "votes" : 3},
{"comment" : "free watches", "author" : "Alice", "votes" : -5}
]});
db.blog.updateOne({"comments.author" : "John"},
{"$set" : {"comments.$.author" : "Jim"}});
db.blog.findOne();
{
"_id": {
"$oid": "61825375fc84f6cbde3299f9"
},
"content": "...",
"comments": [
{
"comment": "good post",
"author": "Jim",
"votes": 0
},
{
"comment": "i thought it was too short",
"author": "Claire",
"votes": 3
},
{
"comment": "free watches",
"author": "Alice",
"votes": -5
}
]
}
- 위치 연산자는 첫 번째로 일치하는 요소만 갱신합니다.
- 따라서 John 이 댓글을 2개 이상 남겼다면 처음 남긴 댓글의 작성자명만 변경됩니다.
- 몽고DB 3.6에서는 개별 배열 요소를 갱신하는 배열 필터인 arrayFilters를 도입해 특정 조건에 맞는 배열 요소를 갱신할 수 있습니다.
- 예를들어 반대표가 5표 이상인 댓글을 추가합니다.
use('jbh_MongoDB');
db.blog.drop();
db.blog.posts.drop();
var post_id = 1;
db.blog.insertOne({"post" : post_id, "comments" : [{"comment" : "good post", "author" : "John", "votes" : 0},
{"comment" : "i thought it was too short", "author" : "Claire", "votes" : 3},
{"comment" : "free watches", "author" : "Alice", "votes" : -5}
]});
db.blog.updateOne({"post" : post_id},
{"$set" : {"comments.$[elem].hidden" : true}},
{arrayFilters: [{"elem.votes" : {$lte: -5}}]});
db.blog.findOne();
{
"_id": {
"$oid": "61825508e93d921e76e70612"
},
"post": 1,
"comments": [
{
"comment": "good post",
"author": "John",
"votes": 0
},
{
"comment": "i thought it was too short",
"author": "Claire",
"votes": 3
},
{
"comment": "free watches",
"author": "Alice",
"votes": -5,
"hidden": true
}
]
}
- 위의 예제는 "comments" 배열의 각 일치 요소에 대한 식별자로 elem을 정의했습니다.
- elem이 식별한 댓글의 투표값이 -5 이하면 "comments" 도큐먼트에 "hidden" 필드를 추가하고 값이 true 로 설정됩니다.
728x90
'Database(데이터베이스) > NOSQL - MongoDB' 카테고리의 다른 글
[MongoDB] 도큐먼트 삭제 (0) | 2021.11.02 |
---|---|
[MongoDB] 도큐먼트 삽입 (1) | 2021.11.01 |
[MongoDB] VSCode에서 MongoDB 명령어 실행하기 (0) | 2021.10.25 |
[MongoDB] 몽고DB 데이터형 (0) | 2021.10.24 |
[MongoDB] 몽고DB VSCode 연동하기 (0) | 2021.10.23 |
이 글을 공유하기