mongodb find doc с соответствующими значениями элемента в массиве объектов [duplicate]

Это следующее:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

То же, что и:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, _x_set, _x_del, 
                    "I'm the 'x' property.")

То же, что и:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, doc="I'm the 'x' property.")
    x = x.setter(_x_set)
    x = x.deleter(_x_del)

То же, что и :

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x
    x = property(_x_get, doc="I'm the 'x' property.")

    def _x_set(self, value):
        self._x = value
    x = x.setter(_x_set)

    def _x_del(self):
        del self._x
    x = x.deleter(_x_del)

Это то же самое, что:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x
17
задан Neil Lunn 12 May 2014 в 04:48
поделиться

3 ответа

Запрос, который вы хотите, следующий:

db.collection.find({"users":{"$not":{"$elemMatch":{"user":{$nin:[1,5,7]}}}}})

Это говорит, что найти все документы, которые не имеют элементов, которые находятся за пределами списка 1,5,7.

27
ответ дан Asya Kamsky 22 August 2018 в 12:25
поделиться
  • 1
    постскриптум этот ответ занимает 10 мс в наборе выборочных данных, сгенерированном из другого «ответа». – Asya Kamsky 13 May 2014 в 04:24
  • 2
    Фантастично, похоже, это дает мне тот же результат, что и запрос в моем вопросе, и он возвращается примерно в 10 раз быстрее. – Wex 13 May 2014 в 05:12
  • 3
    ключ - $ elemMatch, который делает различие, что вы хотите, чтобы определенный элемент удовлетворял определенному условию, в отличие от документа в целом, чтобы удовлетворить это условие. поскольку массивы позволяют "users.user" чтобы иметь несколько значений в одном документе, может быть неоднозначным, означает ли вы какой-либо элемент или конкретный элемент. Как и у вас, любой элемент может удовлетворить $ не один из них, и он становится эквивалентным $ in. $ elemMatch говорит, что один элемент должен быть не одним из них, а это означает, что теперь должен быть другой элемент, который не равен 1,5 или 7. $ теперь не включает те документы – Asya Kamsky 13 May 2014 в 05:45
  • 4
    Хороший ответ. Но стоит отметить, что это также будет включать документы, где users либо отсутствует, либо пуст. – JohnnyHK 13 May 2014 в 13:29
  • 5
    Хорошая точка, @JohnnyHK Я предполагал, что массив пользователей всегда существует и содержит некоторых пользователей. Чтобы исключить этот запрос, может быть & quot; $ и & quot; ed с {"users.user":{$exists:true}} – Asya Kamsky 13 May 2014 в 19:00

Я просто потратил значительную часть своего времени на то, чтобы попытаться реализовать решение Асии выше с объектными сравнениями, а не строгое равенство. Поэтому я решил поделиться этим здесь.

Допустим, вы расширили свой вопрос от userIds до полных пользователей. Вы хотите найти все документы, в которых каждый элемент в массиве users присутствует в другом массиве пользователей: [{user: 1, group: 3}, {user: 2, group: 5},...]

Это не сработает: db.collection.find({"users":{"$not":{"$elemMatch":{"$nin":[{user: 1, group: 3},{user: 2, group: 5},...]}}}}}), потому что $ nin работает только для строгого равенства. Поэтому нам нужно найти другой способ выражения «Не в массиве» для массивов объектов. И использование $where слишком сильно замедлит запрос.

Решение:

db.collection.find({
 "users": {
   "$not": {
     "$elemMatch": {
       // if all of the OR-blocks are true, element is not in array
       "$and": [{
         // each OR-block == true if element != that user
         "$or": [
           "user": { "ne": 1 },
           "group": { "ne": 3 }
         ]
       }, {
         "$or": [
           "user": { "ne": 2 },
           "group": { "ne": 5 }
         ]
       }, {
         // more users...
       }]
     }
   }
 }
})

Чтобы завершить логику: $ elemMatch соответствует всем документам, у которых есть пользователь, который не находится в массив. Таким образом, $ не будет соответствовать всем документам, в которых есть все пользователи в массиве.

0
ответ дан Mark Bryk 22 August 2018 в 12:25
поделиться

Я не знаю о лучшем, но есть несколько разных способов приблизиться к этому, и в зависимости от версии MongoDB у вас есть.

Не слишком уверен, что это ваше намерение или нет , но запрос, как показано, будет соответствовать первому примеру документа, поскольку, поскольку ваша логика реализована, вы соответствуете элементам в массиве этого документа, которые должны содержаться в массиве образцов.

Так что, если вы действительно хотели документ чтобы содержать все эти элементы, тогда оператор $all был бы очевидным выбором:

db.collection.find({ "users.user": { "$all": [ 1, 5, 7 ] } })

Но работая с предположением о том, что ваша логика на самом деле предназначена, по крайней мере в соответствии с предложением вы можете «фильтровать» эти результаты, комбинируя с оператором $in , чтобы на вашем было меньше документов $where ** условие в оценке JavaScript:

db.collection.find({
    "users.user": { "$in": [ 1, 5, 7 ] },
    "$where": function() { 
        var ids = [1, 5, 7];
        return this.users.every(function(u) { 
            return ids.indexOf(u.user) !== -1;
        });
    }
})

И вы получаете индекс, хотя фактический отсканированный будет умножен на количество элементов в массивах из согласованного документа но еще лучше, чем без дополнительного фильтра.

Или, возможно, вы рассматриваете логическую абстракцию оператора $and , используемого в комбинации с $or и, возможно, оператор $size в зависимости от ваших реальных условий массива:

db.collection.find({
    "$or": [
        { "users.user": { "$all": [ 1, 5, 7 ] } },
        { "users.user": { "$all": [ 1, 5 ] } },
        { "users.user": { "$all": [ 1, 7 ] } },
        { "users": { "$size": 1 }, "users.user": 1 },
        { "users": { "$size": 1 }, "users.user": 5 },
        { "users": { "$size": 1 }, "users.user": 7 }
    ]
})

Итак, это поколение всех возможных перестановок вашего условия соответствия, но опять-таки производительность, скорее всего, будет зависеть от вашей доступной установленной версии.

ПРИМЕЧАНИЕ. На самом деле полный сбой в этом случае, поскольку это делает что-то совершенно другое и фактически приводит к логическому $in

< hr>

Альтернативы с каркасом агрегации, ваш пробег может отличаться от того, что наиболее эффективно из-за количества документов в вашей коллекции, одного подхода с MongoDB 2.6 и выше:

db.problem.aggregate([
    // Match documents that "could" meet the conditions
    { "$match": { 
        "users.user": { "$in": [ 1, 5, 7 ] } 
    }},

    // Keep your original document and a copy of the array
    { "$project": {
        "_id": {
            "_id": "$_id",
            "date": "$date",
            "users": "$users"
        },
        "users": 1,
    }},

    // Unwind the array copy
    { "$unwind": "$users" },

    // Just keeping the "user" element value
    { "$group": {
        "_id": "$_id",
        "users": { "$push": "$users.user" }
    }},

    // Compare to see if all elements are a member of the desired match
    { "$project": {
        "match": { "$setEquals": [
            { "$setIntersection": [ "$users", [ 1, 5, 7 ] ] },
            "$users"
        ]}
    }},

    // Filter out any documents that did not match
    { "$match": { "match": true } },

    // Return the original document form
    { "$project": {
        "_id": "$_id._id",
        "date": "$_id.date",
        "users": "$_id.users"
    }}
])

So этот подход использует некоторые недавно введенные операторы set , чтобы сравнить содержимое, хотя, конечно, вам нужно реструктурировать массив, чтобы сделать е. сравнение.

Как указывалось, в $setIsSubset имеется прямой оператор, который делает эквивалент комбинированных операторов выше в одном операторе:

db.collection.aggregate([
    { "$match": { 
        "users.user": { "$in": [ 1,5,7 ] } 
    }},
    { "$project": {
        "_id": {
            "_id": "$_id",
            "date": "$date",
            "users": "$users"
        },
        "users": 1,
    }},
    { "$unwind": "$users" },
    { "$group": {
        "_id": "$_id",
        "users": { "$push": "$users.user" }
    }},
    { "$project": {
        "match": { "$setIsSubset": [ "$users", [ 1, 5, 7 ] ] }
    }},
    { "$match": { "match": true } },
    { "$project": {
        "_id": "$_id._id",
        "date": "$_id.date",
        "users": "$_id.users"
    }}
])

Или с другим подходом, все еще использующим оператор $size от MongoDB 2.6:

db.collection.aggregate([
    // Match documents that "could" meet the conditions
    { "$match": { 
        "users.user": { "$in": [ 1, 5, 7 ] } 
    }},

    // Keep your original document and a copy of the array
    // and a note of it's current size
    { "$project": {
        "_id": {
            "_id": "$_id",
            "date": "$date",
            "users": "$users"
        },
        "users": 1,
        "size": { "$size": "$users" }
    }},

    // Unwind the array copy
    { "$unwind": "$users" },

    // Filter array contents that do not match
    { "$match": { 
        "users.user": { "$in": [ 1, 5, 7 ] } 
    }},

    // Count the array elements that did match
    { "$group": {
        "_id": "$_id",
        "size": { "$first": "$size" },
        "count": { "$sum": 1 }
    }},

    // Compare the original size to the matched count
    { "$project": { 
        "match": { "$eq": [ "$size", "$count" ] } 
    }},

    // Filter out documents that were not the same
    { "$match": { "match": true } },

    // Return the original document form
    { "$project": {
        "_id": "$_id._id",
        "date": "$_id.date",
        "users": "$_id.users"
    }}
])

Что, конечно, все еще можно сделать , хотя немного более длинные в версиях до 2.6:

db.collection.aggregate([
    // Match documents that "could" meet the conditions
    { "$match": { 
        "users.user": { "$in": [ 1, 5, 7 ] } 
    }},

    // Keep your original document and a copy of the array
    { "$project": {
        "_id": {
            "_id": "$_id",
            "date": "$date",
            "users": "$users"
        },
        "users": 1,
    }},

    // Unwind the array copy
    { "$unwind": "$users" },

    // Group it back to get it's original size
    { "$group": { 
        "_id": "$_id",
        "users": { "$push": "$users" },
        "size": { "$sum": 1 }
    }},

    // Unwind the array copy again
    { "$unwind": "$users" },

    // Filter array contents that do not match
    { "$match": { 
        "users.user": { "$in": [ 1, 5, 7 ] } 
    }},

    // Count the array elements that did match
    { "$group": {
        "_id": "$_id",
        "size": { "$first": "$size" },
        "count": { "$sum": 1 }
    }},

    // Compare the original size to the matched count
    { "$project": { 
        "match": { "$eq": [ "$size", "$count" ] } 
    }},

    // Filter out documents that were not the same
    { "$match": { "match": true } },

    // Return the original document form
    { "$project": {
        "_id": "$_id._id",
        "date": "$_id.date",
        "users": "$_id.users"
    }}
])

Это обычно округляет разные способы, пробует их и видит, что лучше всего подходит для вас. По всей вероятности, простая комбинация $in с вашей существующей формой, вероятно, будет лучшей. Но во всех случаях убедитесь, что у вас есть индекс, который можно выбрать:

db.collection.ensureIndex({ "users.user": 1 })

. Это даст вам лучшую производительность, если вы каким-то образом получаете доступ к этому, так как все примеры здесь.


Вердикт

Я был заинтригован этим, поэтому в конечном итоге наработал тестовый пример, чтобы узнать, что было лучше всего. Итак, сначала некоторые генерации тестовых данных:

var batch = [];
for ( var n = 1; n <= 10000; n++ ) {
    var elements = Math.floor(Math.random(10)*10)+1;

    var obj = { date: new Date(), users: [] };
    for ( var x = 0; x < elements; x++ ) {
        var user = Math.floor(Math.random(10)*10)+1,
            group = Math.floor(Math.random(10)*10)+1;

        obj.users.push({ user: user, group: group });
    }

    batch.push( obj );

    if ( n % 500 == 0 ) {
        db.problem.insert( batch );
        batch = [];
    }

} 

С 10000 документами в коллекции со случайными массивами от 1..10 в длине, содержащими случайные значения 1..0, я пришел к числу совпадений 430 документы (уменьшены с 7749 по совпадению $in) со следующими результатами (avg):

  • JavaScript с предложением $in: 420 мс
  • Совокупность с $size : 395ms
  • Агрегат с подсчетом группы: 650 мс
  • Совокупность с двумя операторами набора: 275 мс
  • Совокупность с $setIsSubset: 250 мс

Отмечая, что по образцам, выполненным все, кроме последних двух, была дисперсия peak примерно на 100 мс быстрее, а последние два показали 220 мс ответ. Наибольшие вариации были в запросе JavaScript, который также показал результаты на 100 мс медленнее.

Но дело здесь в том, что касается аппаратного обеспечения, которое на моем ноутбуке под VM не особенно велико, но дает представление.

Таким образом, совокупность и, в частности, версия MongoDB 2.6.1 с установленными операторами явно выигрывают от производительности с дополнительным небольшим усилением, поступающим от $setIsSubset как один оператор.

Это особенно интересно (как указано в методе, совместимом с 2.4) наибольшая стоимость этого процесса будет заключаться в заявлении $unwind (более 100 мс), поэтому при выборе $in, имеющем среднее значение около 32 мс, остальные этапы конвейера выполняются меньше чем 100 мс в среднем. Таким образом, это дает относительное представление об агрегации и производительности JavaScript.

9
ответ дан Neil Lunn 22 August 2018 в 12:25
поделиться
  • 1
    Спасибо, что указал мне в сторону агрегации. Глядя на документы, похоже, setIsSubset также будет уместным. Я посмотрю, как они действуют против того, что у меня уже есть. – Wex 12 May 2014 в 16:38
  • 2
    @Wex вы правы, так как это будет эквивалентно двум операциям набора, которые были использованы в примере. Честно пропустил это, слишком сосредоточив внимание на примерах до 2.6, но стоит добавить и собственный пример. Не пропустив что-то подобное против значительных данных, я не слишком уверен, как меняется производительность. Но у меня все еще есть подозрение, что любая из первых двух форм без метода агрегирования будет наиболее эффективными. – Neil Lunn 12 May 2014 в 23:48
  • 3
    @Wex На самом деле довольно заинтригован тем, каковы ваши результаты могут быть с данными реального мира. Я вернулся к этому с тестовым примером, где результаты были довольно интригующими. – Neil Lunn 13 May 2014 в 03:40
  • 4
    @AsyaKamsky Ну, вы правы, что, несмотря на отрицание индекса, это было бы лучшим решением. Но не было необходимости быть таким же грубым, как вы в ответ. – Neil Lunn 13 May 2014 в 05:23
Другие вопросы по тегам:

Похожие вопросы: