복붙노트

[MONGODB] MongoDB를 집계 $ 그룹은 배열의 길이를 제한

MONGODB

MongoDB를 집계 $ 그룹은 배열의 길이를 제한

나는 필드에 있지만 각 값에 대해 그룹화 문서의 수를 제한에 따라 모든 문서 그룹으로합니다.

각 메시지는 conversation_ID 있습니다. 나는 각 conversation_ID에 대한 메시지의 10 또는 적은 수를 얻을 필요가있다.

나는 다음과 같은 명령에 따라 그룹에 수 있어요하지만를 제한하는 방법을 알아낼 수 없습니다 그룹화 문서 수 떨어져 결과를 슬라이스에서 Message.aggregate ({ '$ 그룹': {_ ID : '$ conversation_ID', MSG를 : { '$ 푸시': {MSGID : '$ _ ID'}}}})

어떻게 10 각 conversation_ID에 대한 MSG를 배열의 길이를 제한하는?

해결법

  1. ==============================

    1.MongoDB를 3.6에서 원래 커서 아래 증명 처리로서, A는 거의 같은 방법으로 "자동 가입"$ 수행 조회를 이용하여 이에 대한 "새로운"방법이있다.

    MongoDB를 3.6에서 원래 커서 아래 증명 처리로서, A는 거의 같은 방법으로 "자동 가입"$ 수행 조회를 이용하여 이에 대한 "새로운"방법이있다.

    이 릴리스에서 당신은이 "참여"에 대한 소스로 $ 조회에 "파이프 라인"인수를 지정할 수 있기 때문에, 이것은 본질적으로 배열은 "제한"항목을 수집하고 $ 일치와 $ 제한을 사용할 수 있다는 것을 의미합니다 :

    db.messages.aggregate([
      { "$group": { "_id": "$conversation_ID" } },
      { "$lookup": {
        "from": "messages",
        "let": { "conversation": "$_id" },
        "pipeline": [
          { "$match": { "$expr": { "$eq": [ "$conversation_ID", "$$conversation" ] } }},
          { "$limit": 10 },
          { "$project": { "_id": 1 } }
        ],
        "as": "msgs"
      }}
    ])
    

    선택적으로 오히려 _id 키를 사용하여 문서를보다 간단하게 값을 배열 항목을하기 위해 $ 조회 후 추가 투사를 추가 할 수 있지만 기본적인 결과는 단순히 위를 수행하여이있다.

    이 뛰어난 SERVER-9277 실제로 직접 "푸시에 제한의"를 요청 여전히 있지만, 이런 식으로 $ 조회를 사용하면 중간에 실행 가능한 대안이다.

    앞서 언급 한 바와 같이이 불가능하지만, 확실히 끔찍한 문제가되지 않습니다.

    당신의 주요 관심사는 당신의 결과 배열은 매우 크게려고하고 있다는 것입니다 실제로 경우 다음 가장 좋은 방법은 개별 쿼리로 각각 별개 "conversation_ID"를 제출 한 다음 결과를 결합하는 것입니다. 매우 MongoDB를 2.6 구문에서 언어 구현이 실제로 무엇인지에 따라 일부 조정이 필요할 수있는 :

    var results = [];
    db.messages.aggregate([
        { "$group": {
            "_id": "$conversation_ID"
        }}
    ]).forEach(function(doc) {
        db.messages.aggregate([
            { "$match": { "conversation_ID": doc._id } },
            { "$limit": 10 },
            { "$group": {
                "_id": "$conversation_ID",
                "msgs": { "$push": "$_id" }
            }}
        ]).forEach(function(res) {
            results.push( res );
        });
    });
    

    그러나 모두는 당신이 피하려고 무엇인지에 따라 달라집니다. 진짜 대답에 따라서 :

    여기서 첫 번째 문제는 "제한"배열로 "푸시"되는 항목의 수에는 기능이 없다는 것이다. 그것은 확실히 우리가하고 싶은 일이지만, 기능은 현재 존재하지 않습니다.

    두 번째 문제는 배열로 모든 항목을 밀어 경우에도, 당신은 집계 파이프 라인에 $ 슬라이스 또는 유사한 연산자를 사용할 수 없다는 것입니다. 그래서 간단한 조작으로 생산 된 배열에서 바로 "10"결과를 얻기에는 현재 방법이 없습니다.

    그러나 당신은 실제로 그룹화 경계에 효율적으로 "슬라이스"를 조작 세트를 생성 할 수 있습니다. 그것은 상당히 관련되어, 예를 들면 여기 난 단지 "여섯"을 "슬라이스"배열 요소를 줄일 수 있습니다. 여기 주된 이유는 과정을 설명하고 "조각"에 원하는 전체를 포함하지 않는 배열과 파괴하지 않고이 작업을 수행하는 방법을 보여주는 것입니다.

    문서의 샘플을 감안할 때 :

    { "_id" : 1, "conversation_ID" : 123 }
    { "_id" : 2, "conversation_ID" : 123 }
    { "_id" : 3, "conversation_ID" : 123 }
    { "_id" : 4, "conversation_ID" : 123 }
    { "_id" : 5, "conversation_ID" : 123 }
    { "_id" : 6, "conversation_ID" : 123 }
    { "_id" : 7, "conversation_ID" : 123 }
    { "_id" : 8, "conversation_ID" : 123 }
    { "_id" : 9, "conversation_ID" : 123 }
    { "_id" : 10, "conversation_ID" : 123 }
    { "_id" : 11, "conversation_ID" : 123 }
    { "_id" : 12, "conversation_ID" : 456 }
    { "_id" : 13, "conversation_ID" : 456 }
    { "_id" : 14, "conversation_ID" : 456 }
    { "_id" : 15, "conversation_ID" : 456 }
    { "_id" : 16, "conversation_ID" : 456 }
    

    당신은 당신의 조건에 따라 그룹화 할 때 "오"와 열 개 요소와 다른 하나 개의 배열을 얻을 것이다 거기에 볼 수 있습니다. 전용 "오"요소 "파괴"배열없이 당신이 여기에서 상단 "육"에 모두를 줄이고 자와 일치하는 것이다.

    그리고 다음 쿼리 :

    db.messages.aggregate([
        { "$group": {
            "_id": "$conversation_ID",
            "first": { "$first": "$_id" },
            "msgs": { "$push": "$_id" },
        }},
        { "$unwind": "$msgs" },
        { "$project": {
            "msgs": 1,
            "first": 1,
            "seen": { "$eq": [ "$first", "$msgs" ] }
        }},
        { "$sort": { "seen": 1 }},
        { "$group": {
            "_id": "$_id",
            "msgs": { 
                "$push": {
                   "$cond": [ { "$not": "$seen" }, "$msgs", false ]
                }
            },
            "first": { "$first": "$first" },
            "second": { "$first": "$msgs" }
        }},
        { "$unwind": "$msgs" },
        { "$project": {
            "msgs": 1,
            "first": 1,
            "second": 1,
            "seen": { "$eq": [ "$second", "$msgs" ] }
        }},
        { "$sort": { "seen": 1 }},
        { "$group": {
            "_id": "$_id",
            "msgs": { 
                "$push": {
                   "$cond": [ { "$not": "$seen" }, "$msgs", false ]
                }
            },
            "first": { "$first": "$first" },
            "second": { "$first": "$second" },
            "third": { "$first": "$msgs" }
        }},
        { "$unwind": "$msgs" },
        { "$project": {
            "msgs": 1,
            "first": 1,
            "second": 1,
            "third": 1,
            "seen": { "$eq": [ "$third", "$msgs" ] },
        }},
        { "$sort": { "seen": 1 }},
        { "$group": {
            "_id": "$_id",
            "msgs": { 
                "$push": {
                   "$cond": [ { "$not": "$seen" }, "$msgs", false ]
                }
            },
            "first": { "$first": "$first" },
            "second": { "$first": "$second" },
            "third": { "$first": "$third" },
            "forth": { "$first": "$msgs" }
        }},
        { "$unwind": "$msgs" },
        { "$project": {
            "msgs": 1,
            "first": 1,
            "second": 1,
            "third": 1,
            "forth": 1,
            "seen": { "$eq": [ "$forth", "$msgs" ] }
        }},
        { "$sort": { "seen": 1 }},
        { "$group": {
            "_id": "$_id",
            "msgs": { 
                "$push": {
                   "$cond": [ { "$not": "$seen" }, "$msgs", false ]
                }
            },
            "first": { "$first": "$first" },
            "second": { "$first": "$second" },
            "third": { "$first": "$third" },
            "forth": { "$first": "$forth" },
            "fifth": { "$first": "$msgs" }
        }},
        { "$unwind": "$msgs" },
        { "$project": {
            "msgs": 1,
            "first": 1,
            "second": 1,
            "third": 1,
            "forth": 1,
            "fifth": 1,
            "seen": { "$eq": [ "$fifth", "$msgs" ] }
        }},
        { "$sort": { "seen": 1 }},
        { "$group": {
            "_id": "$_id",
            "msgs": { 
                "$push": {
                   "$cond": [ { "$not": "$seen" }, "$msgs", false ]
                }
            },
            "first": { "$first": "$first" },
            "second": { "$first": "$second" },
            "third": { "$first": "$third" },
            "forth": { "$first": "$forth" },
            "fifth": { "$first": "$fifth" },
            "sixth": { "$first": "$msgs" },
        }},
        { "$project": {
             "first": 1,
             "second": 1,
             "third": 1,
             "forth": 1,
             "fifth": 1,
             "sixth": 1,
             "pos": { "$const": [ 1,2,3,4,5,6 ] }
        }},
        { "$unwind": "$pos" },
        { "$group": {
            "_id": "$_id",
            "msgs": {
                "$push": {
                    "$cond": [
                        { "$eq": [ "$pos", 1 ] },
                        "$first",
                        { "$cond": [
                            { "$eq": [ "$pos", 2 ] },
                            "$second",
                            { "$cond": [
                                { "$eq": [ "$pos", 3 ] },
                                "$third",
                                { "$cond": [
                                    { "$eq": [ "$pos", 4 ] },
                                    "$forth",
                                    { "$cond": [
                                        { "$eq": [ "$pos", 5 ] },
                                        "$fifth",
                                        { "$cond": [
                                            { "$eq": [ "$pos", 6 ] },
                                            "$sixth",
                                            false
                                        ]}
                                    ]}
                                ]}
                            ]}
                        ]}
                    ]
                }
            }
        }},
        { "$unwind": "$msgs" },
        { "$match": { "msgs": { "$ne": false } }},
        { "$group": {
            "_id": "$_id",
            "msgs": { "$push": "$msgs" }
        }}
    ])
    

    당신은 여섯 개의 항목을 배열에서 최고의 결과를 얻을 :

    { "_id" : 123, "msgs" : [ 1, 2, 3, 4, 5, 6 ] }
    { "_id" : 456, "msgs" : [ 12, 13, 14, 15 ] }
    

    당신이 여기에서 볼 수 있듯이, 많은 재미.

    처음 그룹화 한 후 당신은 기본적으로 배열 결과 스택의 떨어져 $ 첫 번째 값을 "팝업"하고 싶다. 이 과정이 조금 단순화하기 위해, 우리는 실제로는 초기 작동 할. 프로세스가되도록 :

    $ 콘드와 마지막 동작은 "슬라이스"카운트가 배열 구성원보다 큰 경우 미래의 반복은 단지 반복 배열의 마지막 값을 추가하지 않는 있는지가 확인하는 것입니다.

    즉, 전체 프로세스는 "조각"을 원하는대로 많은 항목으로 반복 될 필요가있다. 이미 초기 그룹에서 "첫 번째"항목을 발견 원하는 슬라이스 결과 수단 N-1 번 반복하는 때문이다.

    마지막 단계는 마지막으로 같이 결과를 배열로 모든 것을 다시 변환 정말 그냥 옵션 그림입니다. 그래서 정말 그냥 조건들이 일치하는 위치에 따라 항목 또는 false 다시 밀어 그리고 마지막으로 각각 "필터링"밖으로 모두 거짓 값을 최종 배열은 "여섯"그래서와 "오"회원.

    그래서이이를 수용하기 위해 표준 조작하지 않고, 할 수 있습니다뿐만 아니라 "한계"배열의 5 개 또는 10 또는 어떤 항목에 푸시. 당신이 정말로 그것을해야한다면, 다음이 최선의 방법입니다.

    당신은 아마도 맵리 듀스와 함께이 문제를 접근하고 모두 함께 통합 프레임 워크를 버릴 수 있습니다. 그 결과 "제한"에 자바 스크립트 슬라이스를 사용하는 동안 (합리적인 범위 내에서) 내가 걸릴 접근 방식은, 효과적으로 것과 서버와 축적 배열에 메모리 해시 맵을하는 것입니다 :

    db.messages.mapReduce(
        function () {
    
            if ( !stash.hasOwnProperty(this.conversation_ID) ) {
                stash[this.conversation_ID] = [];
            }
    
            if ( stash[this.conversation_ID.length < maxLen ) {
                stash[this.conversation_ID].push( this._id );
                emit( this.conversation_ID, 1 );
            }
    
        },
        function(key,values) {
            return 1;   // really just want to keep the keys
        },
        { 
            "scope": { "stash": {}, "maxLen": 10 },
            "finalize": function(key,value) {
                return { "msgs": stash[key] };                
            },
            "out": { "inline": 1 }
        }
    )
    

    그래서는 기본적으로 배열 결코 당신이 당신의 결과에서 인출 할 최대 크기를 초과하지와 방출 "키를"일치 "메모리"개체를 만듭니다. 최대 스택이 충족 될 때 또한이 심지어 항목 "발광"을 귀찮게하지 않습니다.

    줄이기 부분은 사실 본질적으로 "키"를 하나의 값으로 감소 이외의 아무것도하지 않는다. 우리의 감속기가 호출되지 않은 경우에만 1 값이 키에 대한 존재한다면 그냥 같은 사실이 될 것이다, 종료 처리 기능은 최종 출력에 "숨겨 놓은"키를 매핑을 담당한다.

    이것의 효과는 출력의 크기에 따라 다르며, 자바 스크립트의 평가는 더 빠른 파이프 라인에 큰 배열을 처리하는 것보다 빠르고 확실하지 않지만 아마도.

    실제로 모두 편리하게 될 것 "$ 푸시"와 "$ addToSet"에 "슬라이스"연산자 심지어 "한계"를 가지고있는 JIRA 이슈를 투표. 개인적으로 처리 할 때 적어도 약간의 수정은 "현재 인덱스"값을 노출하는 $ 맵 운영자에게 할 수 있다는 희망. 즉 효율적으로 "슬라이스"및 기타 작업을 허용합니다.

    정말 당신은 코드에 "생성"필요한 모든 반복이 최대를 원하는 것입니다. 대답은 여기에 충분히 사랑 및 / 또는 다른 시간 나는 tuits에 가지고 보류를 얻을 경우, 나는이 작업을 수행하는 방법을 설명하는 몇 가지 코드를 추가 할 수 있습니다. 이미 합리적으로 긴 반응이다.

    코드 파이프 라인을 생성합니다 :

    var key = "$conversation_ID";
    var val = "$_id";
    var maxLen = 10;
    
    var stack = [];
    var pipe = [];
    var fproj = { "$project": { "pos": { "$const": []  } } };
    
    for ( var x = 1; x <= maxLen; x++ ) {
    
        fproj["$project"][""+x] = 1;
        fproj["$project"]["pos"]["$const"].push( x );
    
        var rec = {
            "$cond": [ { "$eq": [ "$pos", x ] }, "$"+x ]
        };
        if ( stack.length == 0 ) {
            rec["$cond"].push( false );
        } else {
            lval = stack.pop();
            rec["$cond"].push( lval );
        }
    
        stack.push( rec );
    
        if ( x == 1) {
            pipe.push({ "$group": {
               "_id": key,
               "1": { "$first": val },
               "msgs": { "$push": val }
            }});
        } else {
            pipe.push({ "$unwind": "$msgs" });
            var proj = {
                "$project": {
                    "msgs": 1
                }
            };
    
            proj["$project"]["seen"] = { "$eq": [ "$"+(x-1), "$msgs" ] };
    
            var grp = {
                "$group": {
                    "_id": "$_id",
                    "msgs": {
                        "$push": {
                            "$cond": [ { "$not": "$seen" }, "$msgs", false ]
                        }
                    }
                }
            };
    
            for ( n=x; n >= 1; n-- ) {
                if ( n != x ) 
                    proj["$project"][""+n] = 1;
                grp["$group"][""+n] = ( n == x ) ? { "$first": "$msgs" } : { "$first": "$"+n };
            }
    
            pipe.push( proj );
            pipe.push({ "$sort": { "seen": 1 } });
            pipe.push(grp);
        }
    }
    
    pipe.push(fproj);
    pipe.push({ "$unwind": "$pos" });
    pipe.push({
        "$group": {
            "_id": "$_id",
            "msgs": { "$push": stack[0] }
        }
    });
    pipe.push({ "$unwind": "$msgs" });
    pipe.push({ "$match": { "msgs": { "$ne": false } }});
    pipe.push({
        "$group": {
            "_id": "$_id",
            "msgs": { "$push": "$msgs" }
        }
    }); 
    

    즉 $ 그룹에 $ 언 와인드에서 단계를 MAXLEN에 기본 반복적 인 접근 방식까지를 작성합니다. 필요한 최종 예측하고 "중첩"조건 문의 사항이있는도 내장. 마지막은 기본적으로이 질문에 취한 방법입니다 :

    절 보증 순서에서와 MongoDB를의 $?

  2. ==============================

    2.은 $ 슬라이스 연산자 (나는이 대답에 제안처럼 편집하기 전에) 당신은이 작업을 수행 할 수 있도록 집계 연산자되지 않습니다 :

    은 $ 슬라이스 연산자 (나는이 대답에 제안처럼 편집하기 전에) 당신은이 작업을 수행 할 수 있도록 집계 연산자되지 않습니다 :

    db.messages.aggregate([
       { $group : {_id:'$conversation_ID',msgs: { $push: { msgid:'$_id' }}}},
       { $project : { _id : 1, msgs : { $slice : 10 }}}]);
    

    닐의 대답은 매우 자세한이지만, (이 사용 사례를 맞는 경우) 당신은 약간 다른 접근 방식을 사용할 수 있습니다. 당신은 당신의 결과와 새로운 컬렉션에 출력을 집계 할 수 있습니다 :

    db.messages.aggregate([
       { $group : {_id:'$conversation_ID',msgs: { $push: { msgid:'$_id' }}}},
       { $out : "msgs_agg" }
    ]);
    

    운영자 밖으로 $ 새로운 컬렉션에 집계 결과를 기록합니다. 그런 다음 $ 슬라이스 연산자 결과를 프로젝트 쿼리 규칙 찾기를 사용할 수 있습니다 :

    db.msgs_agg.find({}, { msgs : { $slice : 10 }});
    

    이 테스트 문서의 경우 :

    > db.messages.find().pretty();
    { "_id" : 1, "conversation_ID" : 123 }
    { "_id" : 2, "conversation_ID" : 123 }
    { "_id" : 3, "conversation_ID" : 123 }
    { "_id" : 4, "conversation_ID" : 123 }
    { "_id" : 5, "conversation_ID" : 123 }
    { "_id" : 7, "conversation_ID" : 1234 }
    { "_id" : 8, "conversation_ID" : 1234 }
    { "_id" : 9, "conversation_ID" : 1234 }
    

    그 결과는 다음과 같습니다

    > db.msgs_agg.find({}, { msgs : { $slice : 10 }});
    { "_id" : 1234, "msgs" : [ { "msgid" : 7 }, { "msgid" : 8 }, { "msgid" : 9 } ] }
    { "_id" : 123, "msgs" : [ { "msgid" : 1 }, { "msgid" : 2 }, { "msgid" : 3 }, 
                              { "msgid" : 4 }, { "msgid" : 5 } ] }
    

    편집하다

    글쎄, 확실히이 방식은 거대한 컬렉션으로 확장되지 않습니다. 당신이 큰 집계 파이프 라인 또는 큰 사용을 고려하고 있기 때문에 그러나, "실시간"요청 아마 당신은이를 사용하지 않습니다 작업을지도 - 줄일 수 있습니다.

    이 방법의 많은 단점이 있습니다 16메가바이트 BSON 제한하면, 집합 거대한 문서 작성 디스크 IO 증가 디스크 공간 / 복제와 메모리를 낭비하는 경우가 ...

    이 방법의 장점 : 간단한 변화에 따라서 쉽게 구현하고 있습니다. 컬렉션은 거의 업데이트되지 않은 경우 당신은 캐시처럼이 "밖으로"모음을 사용할 수 있습니다. 이 방법은 당신이 집계 작업을 여러 번 수행 할 필요가 없습니다 것입니다 그리고 당신은 심지어 "밖으로"컬렉션 "실시간"클라이언트 요청을 지원할 수 있습니다. 데이터를 새로 고치려면 주기적으로 (즉, 실행 야간, 예를 들어 백그라운드 작업에서) 집계를 할 수 있습니다.

    이 코멘트에 말한 것처럼 이것은 쉬운 문제가 아니고,이에 대한 완벽한 해결책이없는 (아직이!). 나는 벤치 마크에 당신까지 및 사용 사례에 가장 적합한 무엇을 결정, 당신이 사용할 수있는 또 다른 접근 방식을 보여 주었다.

  3. from https://stackoverflow.com/questions/24463689/mongodb-aggregation-group-restrict-length-of-array by cc-by-sa and MIT license