Как найти mongodb для последнего элемента массива?

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

// ie comments[0].by == "Abe" db.example.find( { "comments.0.by" : "Abe" } ) 

но как я могу искать, используя последний элемент в качестве критериев? т.е.

 db.example.find( { "comments.last.by" : "Abe" } ) 

Кстати, я использую php

Solutions Collecting From Web of "Как найти mongodb для последнего элемента массива?"

Я знаю, что этот вопрос старый, но я нашел его в Google после ответа на аналогичный новый вопрос . Поэтому я думал, что это заслуживает такого же лечения.

Вы можете избежать хита производительности $ where , используя вместо этого агрегат :

 db.example.aggregate([ // Use an index, which $where cannot to narrow down {$match: { "comments.by": "Abe" }}, // De-normalize the Array {$unwind: "$comments"}, // The order of the array is maintained, so just look for the $last by _id {$group: { _id: "$_id", comments: {$last: "$comment"} }}, // Match only where that $last comment by `by.Abe` {$match: { "comments.by": "Abe" }}, // Retain the original _id order {$sort: { _id: 1 }} ]) 

И это должно зависеть от $ where, так как мы смогли сузить документы, в которых в первую очередь был комментарий «Abe». Как предупреждалось, $, где собирается тестировать каждый документ в коллекции и никогда не использовать индекс, даже если он используется.

Конечно, вы также можете сохранить исходный документ, используя описанную здесь технику , поэтому все будет работать так же, как find() .

Просто пища для размышлений для тех, кто это находит.


Обновление для современных выпусков MongoDB

В современных выпусках добавлено $redact конвейера $redact а также $arrayElemAt (последнее из 3.2, так что это была бы минимальная версия здесь), которая в комбинации позволяла бы логическому выражению проверять последний элемент массива без обработки $unwind этап:

 db.example.aggregate([ { "$match": { "comments.by": "Abe" }}, { "$redact": { "$cond": { "if": { "$eq": [ { "$arrayElemAt": [ { "$map": { "input": "$comments", "as": "c", "in": "$$c.by" }}, -1 ], }, "Abe" ] }, "then": "$$KEEP", "else": "$$PRUNE" } }} ]) 

Логика здесь выполняется в сравнении, где $arrayElemAt получает последний индекс массива -1 , который преобразуется только в массив значений в свойстве "by" через $map . Это позволяет сравнивать одно значение с требуемым параметром "Abe" .

Это было бы, безусловно, наиболее эффективным решением для сопоставления последнего элемента внутри массива.

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

 { '_id': 'foo'; 'comments' [ { 'value': 'comment #1', 'by': 'Ford' }, { 'value': 'comment #2', 'by': 'Arthur' }, { 'value': 'comment #3', 'by': 'Zaphod' } ], 'last_comment': { 'value': 'comment #3', 'by': 'Zaphod' } } 

Конечно, вы будете дублировать некоторые данные, но по крайней мере вы можете установить эти данные с помощью $set вместе с $push для comment .

 $comment = array( 'value' => 'comment #3', 'by' => 'Zaphod', ); $collection->update( array( '_id' => 'foo' ), array( '$set' => array( 'last_comment' => $comment ), '$push' => array( 'comments' => $comment ) ) ); 

Найти последнее легко!

Вы можете сделать это с помощью оператора $where :

 db.example.find({ $where: 'this.comments.length && this.comments[this.comments.length-1].by === "Abe"' }) 

Обычно применяются медленные ограничения производительности за $where . Однако вы можете помочь с этим, включив в свой запрос "comments.by": "Abe" :

 db.example.find({ "comments.by": "Abe", $where: 'this.comments.length && this.comments[this.comments.length-1].by === "Abe"' }) 

Таким образом, $where только нужно оценивать документы, содержащие комментарии Абэ, и новый термин сможет использовать индекс на "comments.by" .

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

 db.example.find({ "comments:{$slice:-1}.by" : "Abe" } 

// … или

 db.example.find({ "comments.by" : "Abe" } 

Это по умолчанию принимает последний элемент в массиве.