MongoDB의 $ in 절이 순서를 보장합니까?


답변:


78

언급했듯이 $ in 절의 배열에서 인수 순서는 문서 검색 방법의 순서를 반영하지 않습니다. 물론 그것은 자연 순서이거나 표시된 것처럼 선택한 인덱스 순서입니다.

이 순서를 유지해야하는 경우 기본적으로 두 가지 옵션이 있습니다.

따라서 _id문서 의의 값을 $inas에 전달 될 배열과 일치한다고 가정 해 보겠습니다 [ 4, 2, 8 ].

Aggregate를 사용한 접근


var list = [ 4, 2, 8 ];

db.collection.aggregate([

    // Match the selected documents by "_id"
    { "$match": {
        "_id": { "$in": [ 4, 2, 8 ] },
    },

    // Project a "weight" to each document
    { "$project": {
        "weight": { "$cond": [
            { "$eq": [ "$_id", 4  ] },
            1,
            { "$cond": [
                { "$eq": [ "$_id", 2 ] },
                2,
                3
            ]}
        ]}
    }},

    // Sort the results
    { "$sort": { "weight": 1 } }

])

그래서 그것은 확장 된 형태가 될 것입니다. 기본적으로 여기서 일어나는 것은 값의 배열이 전달되는 것처럼 $in"중첩 된"$cond 을 테스트하고 적절한 가중치를 할당하기 위해 문을 입니다. "가중치"값은 배열의 요소 순서를 반영하므로 필요한 순서로 결과를 얻기 위해 해당 값을 정렬 단계로 전달할 수 있습니다.

물론 실제로 다음과 같이 코드에서 파이프 라인 문을 "빌드"합니다.

var list = [ 4, 2, 8 ];

var stack = [];

for (var i = list.length - 1; i > 0; i--) {

    var rec = {
        "$cond": [
            { "$eq": [ "$_id", list[i-1] ] },
            i
        ]
    };

    if ( stack.length == 0 ) {
        rec["$cond"].push( i+1 );
    } else {
        var lval = stack.pop();
        rec["$cond"].push( lval );
    }

    stack.push( rec );

}

var pipeline = [
    { "$match": { "_id": { "$in": list } }},
    { "$project": { "weight": stack[0] }},
    { "$sort": { "weight": 1 } }
];

db.collection.aggregate( pipeline );

mapReduce를 사용한 접근


물론 그 모든 것이 당신의 감성에 무거워 보인다면 mapReduce를 사용하여 똑같은 일을 할 수 있습니다.

var list = [ 4, 2, 8 ];

db.collection.mapReduce(
    function () {
        var order = inputs.indexOf(this._id);
        emit( order, { doc: this } );
    },
    function() {},
    { 
        "out": { "inline": 1 },
        "query": { "_id": { "$in": list } },
        "scope": { "inputs": list } ,
        "finalize": function (key, value) {
            return value.doc;
        }
    }
)

그리고 이것은 기본적으로 출력 된 "키"값이 입력 배열에서 발생하는 방법의 "인덱스 순서"에있는 것에 의존합니다.


따라서 기본적으로 입력 목록의 순서를 $in결정된 순서로 이미 목록이있는 조건 으로 유지하는 방법입니다 .


2
좋은 대답입니다. 필요한 사람들을 위해 여기에
Lawrence Jones

1
@NeilLunn 집계를 사용하여 접근 방식을 시도했지만 ID와 무게를 얻었습니다. 게시물 (객체)을 검색하는 방법을 알고 있습니까?
Juanjo Lainez Reche

1
@NeilLunn 실제로했습니다 (여기에 stackoverflow.com/questions/27525235/… ) 그러나 유일한 의견은 여기에 언급 된 것이 었습니다. 비록 제가 질문을 게시하기 전에 이것을 확인 했음에도 불구하고. 거기에서 나를 도울 수 있습니까? 감사합니다!
Juanjo Lainez Reche

1
이것이 오래되었다는 것을 알지만 inputs.indexOf ()가 this._id와 일치하지 않는 이유를 디버깅하는 데 많은 시간을 낭비했습니다. 객체 Id의 값을 반환하는 경우 다음 구문을 선택해야 할 수 있습니다. obj.map = function () {for (var i = 0; i <inputs.length; i ++) {if (this. _id.equals (inputs [i])) {var order = i; }} emit (order, {doc : this}); };
NoobSter

1
원래 필드도 모두 포함하려면 "$ project"대신 "$ addFields"를 사용할 수 있습니다.
Jodo

39

MongoDB verion> = 3.4 에만 적용되는 집계 쿼리를 사용하는 또 다른 방법 -

신용은이 멋진 블로그 게시물로 이동 합니다.

이 순서로 가져올 예제 문서-

var order = [ "David", "Charlie", "Tess" ];

쿼리-

var query = [
             {$match: {name: {$in: order}}},
             {$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}},
             {$sort: {"__order": 1}}
            ];

var result = db.users.aggregate(query);

사용 된 이러한 집계 연산자를 설명하는 게시물의 또 다른 인용문-

"$ addFields"단계는 3.4에서 새로 추가되었으며 다른 모든 기존 필드를 몰라도 기존 문서에 새 필드를 "$ project"할 수 있습니다. 새로운 "$ indexOfArray"표현식은 주어진 배열에서 특정 요소의 위치를 ​​반환합니다.

기본적으로 addFields연산자는 order찾을 때 모든 문서에 새 필드를 추가 하고이 order필드는 우리가 제공 한 배열의 원래 순서를 나타냅니다. 그런 다음이 필드를 기준으로 문서를 정렬합니다.


순서 배열을 쿼리의 변수로 저장하는 방법이 있으므로 배열이 큰 경우 동일한 배열에 대한 대규모 쿼리가 두 번 발생하지 않습니까?
Ethan SK

24

을 사용하지 않으려면 aggregate다른 해결책은 find다음 을 사용 하여 문서 결과를 클라이언트 측 에서 사용 하고 정렬하는 것입니다 array#sort.

$in값이 숫자와 같은 기본 유형 인 경우 다음 과 같은 접근 방식을 사용할 수 있습니다.

var ids = [4, 2, 8, 1, 9, 3, 5, 6];
MyModel.find({ _id: { $in: ids } }).exec(function(err, docs) {
    docs.sort(function(a, b) {
        // Sort docs by the order of their _id values in ids.
        return ids.indexOf(a._id) - ids.indexOf(b._id);
    });
});

상기 중간 $in값이 같은 비 프리미티브 종류 ObjectId들과 같은 다른 접근 방법이 필요 indexOf경우에 참고로 비교한다.

Node.js 4.x +를 사용 하는 경우 함수를 다음과 같이 변경하여이를 사용 Array#findIndex하고 ObjectID#equals처리 할 수 있습니다 sort.

docs.sort((a, b) => ids.findIndex(id => a._id.equals(id)) - 
                    ids.findIndex(id => b._id.equals(id)));

또는 모든 Node.js 버전, 밑줄 / lodash 포함 findIndex:

docs.sort(function (a, b) {
    return _.findIndex(ids, function (id) { return a._id.equals(id); }) -
           _.findIndex(ids, function (id) { return b._id.equals(id); });
});

equal 함수는 id 속성을 id 'return a.equals (id);'와 비교하는 방법을 어떻게 알 수 있습니까? A는 해당 모델에 대해 반환 된 모든 속성을 보유합니다.
lboyel 2016 년

1
@lboyel 나는 그것이 영리하다는 것을 의미하지는 않았지만 :-) Mongoose를 사용 Document#equals하여 문서의 _id필드 와 비교 했기 때문에 작동했습니다 . _id비교를 명확 하게하기 위해 업데이트되었습니다 . 질문 주셔서 감사합니다.
JohnnyHK

6

JonnyHK 의 솔루션과 유사하게 EcmaScript 2015 findmapArray.prototype.find함수 조합을 사용하여 클라이언트 (클라이언트가 JavaScript에있는 경우)에서 반환 된 문서를 재정렬 할 수 있습니다 .

Collection.find({ _id: { $in: idArray } }).toArray(function(err, res) {

    var orderedResults = idArray.map(function(id) {
        return res.find(function(document) {
            return document._id.equals(id);
        });
    });

});

몇 가지 참고 사항 :

  • 위의 코드는 Mongoose가 아닌 Mongo Node 드라이버를 사용하고 있습니다.
  • idArray배열입니다.ObjectId
  • 이 메서드의 성능 대 정렬을 테스트하지는 않았지만 반환 된 각 항목 (매우 일반적 임)을 조작해야하는 경우 map콜백 에서 수행 하여 코드를 단순화 할 수 있습니다.

5

mongo가 배열을 반환 한 후 결과를 정렬하는 쉬운 방법은 id를 키로 사용하여 객체를 만든 다음 지정된 _id에 매핑하여 올바르게 정렬 된 배열을 반환하는 것입니다.

async function batchUsers(Users, keys) {
  const unorderedUsers = await Users.find({_id: {$in: keys}}).toArray()
  let obj = {}
  unorderedUsers.forEach(x => obj[x._id]=x)
  const ordered = keys.map(key => obj[key])
  return ordered
}

1
이것은 내가 필요한 것을 정확히 수행하며 상위 댓글보다 훨씬 간단합니다.
dyarbrough

@dyarbrough이 솔루션은 모든 문서를 가져 오는 쿼리에서만 작동합니다 (제한 또는 건너 뛰기 없음). 상단 댓글은 더 복잡하지만 모든 시나리오에서 작동합니다.
marian2js

3

항상? 못. 순서는 항상 동일합니다 : 정의되지 않음 (아마도 문서가 저장되는 물리적 순서). 당신이 그것을 분류하지 않는 한.


$natural일반적으로 물리적 인 것이 아니라 논리적 인 주문
Sammaye 2014-04-02

3

이 질문이 Mongoose JS 프레임 워크와 관련이 있다는 것을 알고 있지만 중복 된 것은 일반적이므로 여기에 Python (PyMongo) 솔루션을 게시하는 것이 좋습니다.

things = list(db.things.find({'_id': {'$in': id_array}}))
things.sort(key=lambda thing: id_array.index(thing['_id']))
# things are now sorted according to id_array order

1

이것이 오래된 스레드라는 것을 알고 있지만 배열의 Id 값을 반환하는 경우이 구문을 선택해야 할 수 있습니다. mongo ObjectId 형식과 일치하는 indexOf 값을 얻을 수없는 것 같습니다.

  obj.map = function() {
    for(var i = 0; i < inputs.length; i++){
      if(this._id.equals(inputs[i])) {
        var order = i;
      }
    }
    emit(order, {doc: this});
  };

'ObjectId ()'래퍼를 포함하지 않고 mongo ObjectId .toString을 변환하는 방법-값만?


0

$ or 절로 주문을 보장 할 수 있습니다.

따라서 $or: [ _ids.map(_id => ({_id}))]대신 사용하십시오.


2
$or해결 방법은 일을하지 V2.6 이후 .
JohnnyHK

0

이것은 Mongo에서 결과를 가져온 후의 코드 솔루션입니다. 맵을 사용하여 색인을 저장 한 다음 값을 교환합니다.

catDetails := make([]CategoryDetail, 0)
err = sess.DB(mdb).C("category").
    Find(bson.M{
    "_id":       bson.M{"$in": path},
    "is_active": 1,
    "name":      bson.M{"$ne": ""},
    "url.path":  bson.M{"$exists": true, "$ne": ""},
}).
    Select(
    bson.M{
        "is_active": 1,
        "name":      1,
        "url.path":  1,
    }).All(&catDetails)

if err != nil{
    return 
}
categoryOrderMap := make(map[int]int)

for index, v := range catDetails {
    categoryOrderMap[v.Id] = index
}

counter := 0
for i := 0; counter < len(categoryOrderMap); i++ {
    if catId := int(path[i].(float64)); catId > 0 {
        fmt.Println("cat", catId)
        if swapIndex, exists := categoryOrderMap[catId]; exists {
            if counter != swapIndex {
                catDetails[swapIndex], catDetails[counter] = catDetails[counter], catDetails[swapIndex]
                categoryOrderMap[catId] = counter
                categoryOrderMap[catDetails[swapIndex].Id] = swapIndex
            }
            counter++
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.