12. MongoDB Query Part 2. - findAndModify

이번 포스팅에서는 findAndModify를 이용하여 도큐먼트를 업데이트하는 방법에 대해 알아보도록 하겠습니다.

findAndModify는 제시된 기준의 아이템을 찾고 업데이트하는 일련의 과정을 한 번에 처리할 수 있는 편리한 명령입니다.

findAndModify 명령어의 인자에는 sort 기능이 있는데 지금까지 다룬 적이 없으므로 이 부분을 우선 이해하고 넘어가도록 하겠습니다.

다음과 같이 데이터를 준비하도록 하겠습니다:

db.tasks.insert({todo : "shopping", status : "READY", priority : "4"})
db.tasks.insert({todo : "studying Mongo DB", status : "READY", priority : "1"})
db.tasks.insert({todo : "reporting the current job", status : "DONE", priority : "8"})
db.tasks.insert({todo : "cleaning my room", status : "READY", priority : "3"})
db.tasks.insert({todo : "meeting friends", status : "DONE", priority : "7"})
db.tasks.insert({todo : "depositing money", status : "RUNNING", priority : "6"})
db.tasks.insert({todo : "take a walk", status : "RUNNING", priority : "5"})
db.tasks.insert({todo : "sending an email", status : "READY", priority : "2"})

todo는 "해야 할 일"이며, status는 진행상태이며 "REDAY(준비)", "(처리 중)", "DONE(처리완료)"로 구분됩니다.

priority는 "우선순위"이며 값이 클수록 높은 우선순위를 의미합니다.

그럼 status가 "REDAY"인 것 중에 우선순위가 높은 순서대로 정렬(sort)해 보기 위해 다음과 같이 테스트 해 보겠습니다:

> db.tasks.find({status : "READY"}).sort({priority : -1})
{ "_id" : ObjectId("52eba10e5e3b86c2a732508c"), "todo" : "shopping", "status" : "READY", "priority" : "4" }
{ "_id" : ObjectId("52eba10e5e3b86c2a732508f"), "todo" : "cleaning my room", "status" : "READY", "priority" : "3" }
{ "_id" : ObjectId("52eba10e5e3b86c2a7325093"), "todo" : "sending an email", "status" : "READY", "priority" : "2" }
{ "_id" : ObjectId("52eba10e5e3b86c2a732508d"), "todo" : "studying Mongo DB", "status" : "READY", "priority" : "1" }

priority가 -1인 것은 값을 높은 순서대로 정렬하며, 만약 1인 경우 반대로 낮은 순서로 정렬됩니다 (직접 테스트 해 보시기 바랍니다).

이제 본격적으로 findAndModify를 이해해 보겠습니다. status가 "REDAY"인 것들 중에서 priority가 가장 높은 것을 찾아 status를 "RUNNING"으로 바꾸는 명령을 수행합니다.

다음 명령을 입력해 보겠습니다:

db.tasks.findAndModify({
    query : {status : "READY"},
    sort : {priority : -1},
    update : {"$set" : {status : "RUNNING"}}
})

위의 명령에서 query는 업데이트할 선택 기준을, sort는 정렬의 기준을, update는 선택된 도큐먼트를 업데이트 할 내용을 입력합니다.

이외에 findAndModify의 입력 파라미터들에 대해서는 뒷부분에서 좀 더 자세히 설명하기로 하겠습니다.

수행한 명령에 대한 결과를 살펴보면,

> db.tasks.find()
{ "_id" : ObjectId("52eba10e5e3b86c2a732508c"), "priority" : "4", "status" : "RUNNING", "todo" : "shopping" }
{ "_id" : ObjectId("52eba10e5e3b86c2a732508d"), "todo" : "studying Mongo DB", "status" : "READY", "priority" : "1" }
{ "_id" : ObjectId("52eba10e5e3b86c2a732508e"), "todo" : "reporting the current job", "status" : "DONE", "priority" : "8" }
{ "_id" : ObjectId("52eba10e5e3b86c2a732508f"), "todo" : "cleaning my room", "status" : "READY", "priority" : "3" }
{ "_id" : ObjectId("52eba10e5e3b86c2a7325090"), "todo" : "meeting friends", "status" : "DONE", "priority" : "7" }
{ "_id" : ObjectId("52eba10e5e3b86c2a7325091"), "todo" : "depositing money", "status" : "RUNNING", "priority" : "6" }
{ "_id" : ObjectId("52eba10e5e3b86c2a7325092"), "todo" : "take a walk", "status" : "RUNNING", "priority" : "5" }
{ "_id" : ObjectId("52eba10e5e3b86c2a7325093"), "todo" : "sending an email", "status" : "READY", "priority" : "2" }

인데, 예상대로 status가 "READY"였던 것 중 가장 우선순위가 높았던 "shopping"의 status가 "RUNNING"으로 바뀌었음을 확인할 수 있습니다.

이 명령을 반복적으로 수행하면 우선순위가 높은 순서대로 "RUNNING"으로 바뀌는 것을 확인할 수 있을 것입니다. 만약 더 이상 업데이트 할 것이 없으면 "null"을 반환합니다.

findAndModify의 주요 입력 파라미터를 정리해 보겠습니다 (보다 자세한 파라미터에 대한 내용을 확인하려면 이 곳을 참고하시기 바랍니다).

  • query : document

    • 업데이트 할 도큐먼트의 선택 기준. 한번에 하나의 도큐먼트만 업데이트 가능함.
  • sort : document

    • 업데이트 할 도큐먼트 선택. 도큐먼트의 정렬 순서를 정함.
  • update : document

    • update 또는 remove 둘 중 하나는 반드시 명시되어야 함. 두 개를 동시에 명시할 수는 없음. field: value 형태로 업데이트.
  • remove : boolean

    • "update" 또는 "remove" 둘 중 하나는 반드시 명시되어야 함. 두 개를 동시에 명시할 수는 없음. update field에 명시된 도큐먼트를 제거. 도큐먼트를 제거하려면 "true"로 설정함. 기본값은 "false"임.
  • new : boolean

    • "true"로 설정될 경우 원본을 업데이트 하는 것이 아닌 새로운 문서로 생성하여 업데이트 됨.

이번에는 다음을 입력하여 도큐먼트를 제거해 보겠습니다. 현재 DB 내용은 다음과 같습니다.

> db.tasks.find()
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250ac"), "priority" : "4", "status" : "RUNNING", "todo" : "shopping" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250ad"), "todo" : "studying Mongo DB", "status" : "READY", "priority" : "1" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250ae"), "todo" : "reporting the current job", "status" : "DONE", "priority" : "8" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250af"), "todo" : "cleaning my room", "status" : "READY", "priority" : "3" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250b0"), "todo" : "meeting friends", "status" : "DONE", "priority" : "7" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250b1"), "todo" : "depositing money", "status" : "RUNNING", "priority" : "6" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250b2"), "todo" : "take a walk", "status" : "RUNNING", "priority" : "5" }
{ "_id" : ObjectId("52ebb9ff5e3b86c2a73250b3"), "todo" : "sending an email", "status" : "READY", "priority" : "2" }

현재 완료된("status" : "DONE") 도큐먼트 중 우선순위가 가장 낮은 아이템을 삭제해 보겠습니다:

db.tasks.findAndModify({
    query : {status : "DONE"},
    sort : {priority : 1},
    remove : true
})

결과를 살펴보면,

> db.tasks.find()
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250ac"), "priority" : "4", "status" : "RUNNING", "todo" : "shopping" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250ad"), "todo" : "studying Mongo DB", "status" : "READY", "priority" : "1" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250ae"), "todo" : "reporting the current job", "status" : "DONE", "priority" : "8" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250af"), "todo" : "cleaning my room", "status" : "READY", "priority" : "3" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250b1"), "todo" : "depositing money", "status" : "RUNNING", "priority" : "6" }
{ "_id" : ObjectId("52ebb9fe5e3b86c2a73250b2"), "todo" : "take a walk", "status" : "RUNNING", "priority" : "5" }
{ "_id" : ObjectId("52ebb9ff5e3b86c2a73250b3"), "todo" : "sending an email", "status" : "READY", "priority" : "2" }

이며, 예상대로 친구를 만나는(meeting friends) 일이 삭제되었음을 확인할 수 있을 것입니다.

findAndModifyupdate 명령에 비해 떨어지는 점은 속도입니다. findAndModify가 약간 더 느립니다.