어큐뮬레이터(집계)
정의
$accumulator
사용자 지정 축적자 연산자 를 정의합니다. 축적자는 문서가 파이프라인을 통과할 때 상태(예: 합계, 최대값, 최소값 및 관련 데이터)를 유지하는 연산자입니다.
$accumulator
연산자를 사용하여 자체 JavaScript 함수를 실행하여 MongoDB 쿼리 언어에서 지원하지 않는 동작을 구현합니다.$function
도 참조하세요.$accumulator
이 단계에서 사용할 수 있습니다:중요
집계 연산자 내에서 JavaScript를 실행하면 성능이 저하될 수 있습니다. 제공된 파이프라인 연산자로 애플리케이션의 요구 사항을 충족할 수 없는 경우에만
$accumulator
연산자를 사용합니다.
구문
$accumulator
연산자의 구문은 다음과 같습니다.
{ $accumulator: { init: <code>, initArgs: <array expression>, // Optional accumulate: <code>, accumulateArgs: <array expression>, merge: <code>, finalize: <code>, // Optional lang: <string> } }
필드 | 유형 | 설명 | ||||
---|---|---|---|---|---|---|
문자열 또는 코드 | 상태를 초기화하는 데 사용되는 함수입니다.
| |||||
배열 | 선택 사항.
중요
| |||||
문자열 또는 코드 | 문서를 누적하는 데 사용되는 함수입니다.
| |||||
배열 |
| |||||
문자열 또는 코드 | 두 개의 내부 상태를 병합하는 데 사용되는 함수입니다.
| |||||
문자열 또는 코드 | 선택 사항. 누적 결과를 업데이트하는 데 사용되는 함수입니다.
| |||||
문자열 |
중요현재 |
행동
다음 단계에서는 $accumulator
연산자가 문서를 처리하는 방법을 간략하게 설명합니다.
연산자는 init 함수에 의해 정의된 초기 상태에서 시작됩니다.
각 문서에 대해 연산자는 accumulate 함수를 기반으로 상태를 업데이트합니다. accumulate 함수의 첫 번째 인수는 현재 상태이며 추가 인수는 accumulateArgs 배열에 지정됩니다.
연산자는 여러 중간 상태를 병합해야 하는 경우 병합 함수를 실행합니다. 병합 함수가 호출되는 경우에 대한 자세한 내용은
$merge
으로 두 가지 상태 병합 항목을 참조하세요.finalize 함수가 정의되어 있는 경우 모든 문서가 처리되고 그에 따라 상태가 업데이트되면 finalize 함수가 상태를 최종 출력으로 변환합니다.
두 상태 병합 $merge
내부 연산의 일부로 $accumulator
연산자는 두 개의 개별적인 중간 상태를 병합해야 할 수 있습니다. merge 함수는 연산자가 두 상태를 병합하는 방법을 지정합니다.
병합 함수는 항상 한 번에 상태 2개를 병합합니다. 상태를 3개 이상 병합해야 하는 경우 상태 2개의 결과 병합은 단일 상태로 병합됩니다. 이 프로세스는 모든 상태가 병합될 때까지 반복됩니다.
예를 들어 $accumulator
는 다음 시나리오에서 두 상태를 결합해야 할 수 있습니다.
$accumulator
샤드 클러스터에서 실행됩니다. 연산자는 최종 결과를 얻기 위해 각 샤드의 결과를 병합해야 합니다.단일
$accumulator
작업이 지정된 메모리 제한을 초과했습니다.allowDiskUse
옵션을 지정하면 연산자는 진행 중인 작업을 디스크에 저장하고 메모리에서 작업을 완료합니다. 연산이 완료되면 병합 기능을 사용하여 디스크와 메모리의 결과를 함께 병합합니다.
문서 처리 순서
MongoDB가 init()
, accumulate()
, merge()
함수에 대해 문서를 처리하는 순서는 다를 수 있으며, $accumulator
함수에 해당 문서가 지정된 순서와 다를 수 있습니다.
예를 들어 _id
필드가 알파벳 문자인 일련의 문서를 생각해 보세요.
{ _id: 'a' }, { _id: 'b' }, { _id: 'c' } ... { _id: 'z' }
다음으로 _id
필드를 기준으로 문서를 정렬한 다음 $accumulator
함수를 사용하여 _id
필드 값을 연결하는 집계 파이프라인을 고려합니다.
[ { $sort: { _id: 1 } }, { $group: { _id: null, alphabet: { $accumulator: { init: function() { return "" }, accumulate: function(state, letter) { return(state + letter) }, accumulateArgs: [ "$_id" ], merge: function(state1, state2) { return(state1 + state2) }, lang: "js" } } } } ]
MongoDB는 문서가 정렬된 순서로 처리되는 것을 보장하지 않으므로 alphabet
필드가 반드시 abc...z
로 설정되는 것은 아닙니다.
이 동작으로 인해 $accumulator
함수가 특정 순서로 문서를 처리하고 반환할 필요가 없는지 확인합니다.
Javascript 사용
$accumulator
를 사용하려면 서버측 스크립팅을 활성화해야 합니다.
$accumulator
(또는 $function
, $where
또는 mapReduce
)을 사용하지 않는 경우 서버 측 스크립팅을 비활성화합니다.
mongod
인스턴스의 경우security.javascriptEnabled
구성 옵션 또는 명령줄 옵션을 참조하세요--noscripting
.mongos
인스턴스의 경우security.javascriptEnabled
구성 옵션 또는--noscripting
명령줄 옵션을 참조하세요.In earlier versions, MongoDB does not allow JavaScript execution onmongos
instances.
또한 ➤ 보안 구성 옵션으로 MongoDB 실행을 참조하세요.
예제
$accumulator
를 사용하여 $avg
연산자 구현
참고
이 예시에서는 $accumulator
연산자를 사용해 MongoDB에서 이미 지원되고 있는 $avg
연산자를 구현하는 방법을 안내합니다. 이 예시의 목표는 새로운 기능을 구현하는 것이 아니라 $accumulator
연산자의 동작과 구문을 익숙한 논리로 설명하는 것입니다.
mongosh
에서 다음 문서를 사용하여 books
라는 이름의 샘플 컬렉션을 만듭니다.
db.books.insertMany([ { "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 }, { "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 }, { "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 }, { "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }, { "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 } ])
다음 연산은 groups
(을)를 author
로 문서화하고 $accumulator
(을)를 사용하여 각 저자의 전체 평균 사본 수를 계산합니다.
db.books.aggregate([ { $group : { _id : "$author", avgCopies: { $accumulator: { init: function() { // Set the initial state return { count: 0, sum: 0 } }, accumulate: function(state, numCopies) { // Define how to update the state return { count: state.count + 1, sum: state.sum + numCopies } }, accumulateArgs: ["$copies"], // Argument required by the accumulate function merge: function(state1, state2) { // When the operator performs a merge, return { // add the fields from the two states count: state1.count + state2.count, sum: state1.sum + state2.sum } }, finalize: function(state) { // After collecting the results from all documents, return (state.sum / state.count) // calculate the average }, lang: "js" } } } } ])
결과
이 연산은 다음과 같은 결과를 반환합니다.
{ "_id" : "Dante", "avgCopies" : 1.6666666666666667 } { "_id" : "Homer", "avgCopies" : 10 }
행동
$accumulator
는 count
및 sum
이 모두 0
으로 설정된 초기 상태를 정의합니다. $accumulator
가 처리하는 각 문서에 대해 다음을 통해 상태를 업데이트합니다.
count
를 1씩 증가문서의
copies
필드 값을sum
에 추가합니다. accumulate 함수는 accumulateArgs 필드에 전달되므로copies
필드에 액세스할 수 있습니다.
문서가 처리될 때마다 accumulate 함수는 업데이트된 상태를 반환합니다.
모든 문서가 처리되면 finalize 함수는 사본의 sum
을 문서의 count
로 나누어 평균을 구합니다. 이렇게 하면 finalize 함수가 모든 문서의 누적 sum
및 count
를 수신하므로 실행 중인 계산된 평균을 계속 유지할 필요가 없습니다.
와(과) 비교하기 $avg
이 작업은 $avg
연산자를 사용하는 다음 파이프라인과 동일합니다.
db.books.aggregate([ { $group : { _id : "$author", avgCopies: { $avg: "$copies" } } } ])
을 사용하여 initArgs
그룹별로 초기 상태 변경
initArgs 옵션을 사용하여 $accumulator
의 초기 상태를 변경할 수 있습니다. 예를 들어 다음과 같은 경우에 유용합니다.
사용자의 상태에 없는 필드 값을 사용하여 사용자의 상태에 영향을 미치거나
처리 중인 그룹에 따라 초기 상태를 다른 값으로 설정합니다.
mongosh
에서 다음 문서를 사용하여 restaurants
라는 이름의 샘플 컬렉션을 만듭니다.
db.restaurants.insertMany([ { "_id" : 1, "name" : "Food Fury", "city" : "Bettles", "cuisine" : "American" }, { "_id" : 2, "name" : "Meal Macro", "city" : "Bettles", "cuisine" : "Chinese" }, { "_id" : 3, "name" : "Big Crisp", "city" : "Bettles", "cuisine" : "Latin" }, { "_id" : 4, "name" : "The Wrap", "city" : "Onida", "cuisine" : "American" }, { "_id" : 5, "name" : "Spice Attack", "city" : "Onida", "cuisine" : "Latin" }, { "_id" : 6, "name" : "Soup City", "city" : "Onida", "cuisine" : "Chinese" }, { "_id" : 7, "name" : "Crave", "city" : "Pyote", "cuisine" : "American" }, { "_id" : 8, "name" : "The Gala", "city" : "Pyote", "cuisine" : "Chinese" } ])
사용자가 이 데이터를 쿼리하여 레스토랑을 찾을 수 있는 애플리케이션이 있다고 가정해 보겠습니다. 사용자가 거주하고 있는 도시에 대한 결과를 더 많이 표시하는 것이 유용할 수 있습니다. 이 예제에서는 사용자의 도시가 userProfileCity
이라는 변수에서 호출된다고 가정합니다.
다음 집계 파이프라인은 city
별로 문서를 groups
작업합니다. 이 연산은 $accumulator
를 사용하여 레스토랑의 도시가 사용자 프로필의 도시와 일치하는지 여부에 따라 각 도시에서 다른 수의 결과를 표시합니다.
1 db.restaurants.aggregate([ 2 { 3 $group : 4 { 5 _id : { city: "$city" }, 6 restaurants: 7 { 8 $accumulator: 9 { 10 init: function(city, userProfileCity) { // Set the initial state 11 return { 12 max: city === userProfileCity ? 3 : 1, // If the group matches the user's city, return 3 restaurants 13 restaurants: [] // else, return 1 restaurant 14 } 15 }, 16 17 initArgs: ["$city", <userProfileCity>], // Argument to pass to the init function 18 19 accumulate: function(state, restaurantName) { // Define how to update the state 20 if (state.restaurants.length < state.max) { 21 state.restaurants.push(restaurantName); 22 } 23 return state; 24 }, 25 26 accumulateArgs: ["$name"], // Argument required by the accumulate function 27 28 merge: function(state1, state2) { 29 return { 30 max: state1.max, 31 restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) 32 } 33 }, 34 35 finalize: function(state) { // Adjust the state to only return field we need 36 return state.restaurants 37 } 38 39 lang: "js" 40 } 41 } 42 } 43 } 44 ])
결과
userProfileCity
의 값이 Bettles
인 경우, 연산은 다음과 같은 결과를 반환합니다.
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury", "Meal Macro", "Big Crisp" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave" ] } }
userProfileCity
의 값이 Onida
인 경우, 연산은 다음과 같은 결과를 반환합니다.
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap", "Spice Attack", "Soup City" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave" ] } }
userProfileCity
의 값이 Pyote
인 경우, 연산은 다음과 같은 결과를 반환합니다.
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave", "The Gala" ] } }
userProfileCity
값이 다른 값인 경우 이 작업은 다음 결과를 반환합니다.
{ "_id" : { "city" : "Bettles" }, "restaurants" : { "restaurants" : [ "Food Fury" ] } } { "_id" : { "city" : "Onida" }, "restaurants" : { "restaurants" : [ "The Wrap" ] } } { "_id" : { "city" : "Pyote" }, "restaurants" : { "restaurants" : [ "Crave" ] } }
행동
init 함수는 max
및 restaurants
필드가 포함된 초기 상태를 정의합니다. max
필드는 해당 그룹의 최대 레스토랑 수를 설정합니다. 문서의 city
필드가 userProfileCity
와 일치하는 경우 해당 그룹에는 최대 3개의 레스토랑이 포함됩니다. 그렇지 않고 문서 _id
가 userProfileCity
와 일치하지 않으면 그룹에는 최대 1개의 레스토랑이 포함됩니다. init 함수는 city
와 userProfileCity
인수 모두를 initArgs 배열에서 받습니다.
$accumulator
가 처리하는 각 문서에 대해 레스토랑의 name
을 restaurants
배열로 푸시합니다. 단 해당 이름의 길이가 restaurants
길이의 max
값을 넘지 않아야 합니다. 문서가 처리될 때마다 accumulate 함수는 업데이트된 상태를 반환합니다.
병합 함수는 두 상태를 병합하는 방법을 정의합니다. 이 함수는 restaurant
각 상태의 배열을 함께 연결하고, 결과 배열의 길이는 slice() 메서드를 사용하여 max
값을 초과하지 않는지 확인합니다.
모든 문서가 처리되면 finalize 함수는 레스토랑 이름만 반환하도록 결과 상태를 수정합니다. 이 기능이 없으면 max
필드도 출력에 포함되어 해당 애플리케이션에 대한 요구 사항을 충족하지 못합니다.