[Ch3] 도큐먼트 생성, 갱신, 삭제 (갱신 2)

2022. 7. 13. 13:02책/몽고DB 완벽 가이드

3.2.2. Array Operator

◎ $push

{ $push: { <field1>: <value1>, ... } }
  • 배열에 요소를 추가한다.
  • 배열이 이미 존재하면 배열 끝에 요소를 추가하고, 존재하지 않으면 새로운 배열을 생성한다.
  • Array.prototype.push랑 비슷한 느낌으로 돌아간다고 보면 됨.

블로그 게시물에 누가 댓글을 달면 DB에 반영해보자

let firstPost = db.blog.posts.findOne({ "title": "첫 번째 게시글" });
firstPost; 
{
  "_id" : ObjectId("62cd55dae195d477e64034df"),
  "title" : "첫 번째 게시글",
  "content" : "야한 생각 중입니다"
}

db.blog.posts.updateOne(firstPost, {
  "$push": { "comments" : { "name" : "joe", "content": "ㅁㅊ놈아ㅋㅋㅋㅋ" }}}
);

db.blog.posts.find().pretty();

{
  "_id" : ObjectId("62cd55dae195d477e64034df"),
  "title" : "첫 번째 게시글",
  "content" : "야한 생각 중입니다",
  "comments" : [ { "name" : "joe", "content" : "ㅁㅊ놈아ㅋㅋㅋㅋ"} ]
}

위 예시에선 단순히 element를 추가하는 형태지만 더 복잡한 배열 기능에도 사용 가능하며, 이때 제한자 Modifier를 사용한다.

 

 $addToSet

{ $addToSet: { <field1>: <value1>, ... } }
  • 배열에 값을 넣을 때, 해당 값이 이미 배열에 없는 경우에만 삽입해준다
  • 아래 $push + $ne 조합보다 더 많은 일을 할 수 있다
db.people
{ "name": "김씨", "friends": [ "나씨", "박씨", "이씨" ] }

db.people.updateOne({ name: "김씨" }, {
  $addToSet : { friends : 
    { $each : [ "박씨", "이씨", "연씨", "운씨", "서씨" ] } 
  }
});

// 기존에 있던 "박씨", "이씨"는 추가되지 않는다
{ "name": "김씨", "friends": [ "나씨", "박씨", "이씨", "연씨", "운씨", "서씨" ] }

 

 

 $pop

{ $pop: { <field>: <-1 | 1>, ... } }
  • 배열의 첫 번째 or 마지막 요소를 제거한다
  • 배열을 스택이나 큐로 사용할 때 쓰면 좋음
  • -1이면 첫 번째, 1이면 마지막 요소를 제거

db.students
{ { name: "김", score: [ 80, 70, 66, 96 ] }

맨 앞의 80점을 지워보자
db.students.updateOne({ name : "김", {
  $pop : { score : -1 }
});

{ { name: "김", score: [ 70, 66, 96 ] }

 

 $pull

{ $pull: { <field1>: <value | condition>, <field2>: <value | condition>, ... } }
  • 조건에 맞는 모든 요소를 제거한다
  • 특정 값 제거뿐만 아니라 특정 조건에 맞는 도큐먼트도 제거할 수 있다(in nested document)

db.lists
{ date: "2022-07-13", todo: [ "빨래", "청소", "설거지", "공부", "데이트" ] }

데이트를 한다니 괘씸하니 없애보자
db.lists.updateOne({ date: "2022-07-13", {
  $pull : { todo : "데이트" }
});

{ date: "2022-07-13", todo: [ "빨래", "청소", "설거지", "공부" ] }
착해졌다

 

 $ (위치 연산자positional operator)

{ "<array>.$" : value }
  • 쿼리 도큐먼트와 일치하는 배열 요소 및 요소의 위치를 알아낸다
  • 배열의 인덱스를 명시적으로 입력하지 않아도 해당 요소를 찾아내기 위해 쓴다
  • 대충 Array.prototype.findIndex 를 끼얹은 느낌이라고 봐도 된다
  • 만약 일치하는 요소가 여러개면 첫 번째 요소만 반환한다

db.blog.posts
{ id: 288, content : "...", comments : [
  { author: "ㅇㅇ", comment: "ㅄ", vote: 0 }
  { author: "ㄴㄴ", comment: "어휴", vote: 3 }
  { author: "ㅂㅂ", comment: "뭐함?", vote: -5 }
  { author: "ㅅㅅ", comment: "ㅉㅉ", vote: -9 }
]}

여기서 "ㅂㅂ"가 쓴 댓글을 수정하고 싶다면 어떻게 할까?
JS관점으로 보면 아래처럼 수정할 수 있을 것이다.
posts[2].comment = "뭐함 ㅄ아" 

그러나 만약 요소가 100만개쯤 된다고 치면
DB에서 일일이 쿼리해서 "ㄴㄴ"가 몇 번째 위치에 있는지 찾을거임?
이런 불상사를 막기 위해 $ 가 존재한다.

db.blog.updateOne({ "comments.author" : "ㄴㄴ" }, {
  $set : { "comment.$.comment" : "뭐함 ㅄ아" }
});

 

 $[ <identifier > ]

  • arrayFilter 조건에 맞는 요소들에 일시적으로 이름을 달아 식별하기 위해 쓴다
  • 아래 표현식에서 배열의 필터 결괏값에 lessThanFive라는 변수명을 달아주는 역할과 비슷하다고 보면 된다.
    const lessThanFive = Array.filter( val => val <= 5 );

arrayFilters

  • 개별 배열 요소를 갱신하기 위한 필터
  • 특정 조건에 맞는 모든 배열 요소를 갱신할 수 있다

db.blog.posts
{ id: 288, content : "...", comments : [
  { author: "ㅇㅇ", comment: "ㅄ", vote: 0 }
  { author: "ㄴㄴ", comment: "어휴", vote: 3 }
  { author: "ㅂㅂ", comment: "뭐함?", vote: -5 }
  { author: "ㅅㅅ", comment: "ㅉㅉ", vote: -9 }
]}

여기서 vote ≤ -5 인 댓글은 경고를 줘보자

db.blog.posts.updateOne({ id: 288 },
  { $set : { "comments.$[elem].warning" : true } },
  { arrayFilters : [{ "elem.votes" : { $lte : -5 }] },
);

JS로 표현하자면 대충 아래와 비슷한 구문이다
Array
  .filter( comment => comment.vote <= -5)
  .forEach( comment => comment.warning = true )
 

3.2.3. Modifiers

 $each

{ $push: { <field>: { $each: [ <value1>, <value2> ... ] } } }
  • $addToSet 연산자, $push 연산자와 함께 사용 가능한 제한자
  • 요소 여러개를 한번에 추가할 때 사용한다.
  • 한 번에 여러개의 제한자를 동시 사용 가능하다.

예를 들어, 날짜별로 지각생들을 관리하는 컬렉션이 있다고 치자. 
2022-07-19 날짜에 "지각생 번호" : [18, 28, 38] 라는 필드를 추가하려면?

db.students.late.insertOne({ "date" : "2022-07-19" });

db.students.late.updateOne({ "date" : "2022-07-19"}, {
  "$push" : { "지각생 번호" : { "$each" : [18, 28, 38] } }
});

db.students.late.find().pretty()
{ "_id" : ObjectId("62cd67a0e195d477e64034e5"), 
  "date" : "2022-07-19", 
  "지각생 번호" : [ 18, 28, 38 ] }
 
 
 $slice
{ $push: { <field> : { $each: [ <value1>, <value2>, ... ], $slice: <num> } } }
  • $push 연산자를 사용하며 배열의 요소 갯수를 제한하는 용도로 사용된다
  • $slice 단독으론 사용할 수 없고, 반드시 $each 연산자와 함께 호출되어야 한다
  • 만약 $each에 할당할 게 따로 없다면 [ ]로 빈 배열을 전달하면 된다
  • <num> 는 ± 부호를 구분하여 작성한다
      ex) -10 : 20개 추가시 뒷 쪽부터 10개만 추려서 배열에 추가한다
      eX) +10 : 20개 추가시 앞 쪽부터 10개만 추려서 배열에 추가한다

db.students
{ "_id": 1, "scores": [ 40, 50, 60 ] }

db.students.updateOne({ _id: 1 }, {
  $push : { scores: { $each: [ 80, 78, 86 ], $slice: -5 } }
})

{ "_id": 1, "scores" : [ 50, 60, 80, 78, 86 ] }
// slice가 -5로 설정돼있고,
// 배열의 요소가 5개를 초과하는 경우
// 뒷 부분부터 추려서 남기므로
// 맨 앞에 있던 40이 제외됐다

 

 $sort
{ $push: { <field>: { $each: [ <value1>, <value2>, ... ], $sort: <sort specification> } } }
  • $push 연산자와 사용하며, 배열의 요소를 정렬하는데 사용한다
  • <sort specification> 에서 정렬할 기준이 되는 필드와 오름차순(1)/내림차순(-1)을 지정한다
  • $each 연산자와 반드시 함께 호출되어야 한다
  • 만약 $each에 할당할 게 따로 없다면 [ ]로 빈 배열을 전달하면 된다

예를 들어 아래와 같은 컬렉션을 score 오름차순 정리하고 싶다면...
db.students
{  "_id" : 1, "quiz" : [
  { "id" : 1, "score" : 6 },
  { "id" : 2, "score" : 9 },
  { "id" : 3, "score" : 8 },
  { "id" : 4, "score" : 7 },
] }

딱히 추가할 건 없고 배열만 정렬하면 되니까
$each에 빈 배열[]을 넣어주고 $sort를 호출하면 된다

db.students.updateOne({ "_id": 1}, { 
  $push : { quiz: { $each: [], $sort: { score: 1 } } } 
});

{ "_id" : 1, "quiz" : [ 
  { "id" : 1, "score" : 6 }, 
  { "id" : 4, "score" : 7 }, 
  { "id" : 3, "score" : 8 }, 
  { "id" : 2, "score" : 9 } 
]}

 

 

 

3.2.4. Comparison Query Operators

 $ne

{ field: { $ne: value } }
  • 필드 값이 주어진 값과 비교하여 일치하지 않을 때(not equal) 선택한다
  • 배열 연산자와 함께 쓰면 배열을 집합(Set)처럼 다룰 수 있다

db.people
{ "name": "김씨", "friends": [ "나씨", "박씨", "이씨" ] }
{ "name": "나씨", "friends": [ "김씨", "박씨" ] }
{ "name": "박씨", "friends": [ "김씨", "나씨" ] }

"이씨"랑 친구가 아닌 사람들을 찾아서 
"이씨"를 친구목록에 추가해주자

db.people.updateMany(
  { friends : { $ne : "이씨" } },
  { $push : { friends : "이씨" } }
);