Я хочу найти документы, где последние элементы в массиве равны некоторому значению. Элементы массива могут быть доступны по определенной позиции массива:
// ie comments[0].by == "Abe" db.example.find( { "comments.0.by" : "Abe" } )
но как я могу искать, используя последний элемент в качестве критериев? т.е.
db.example.find( { "comments.last.by" : "Abe" } )
Кстати, я использую php
Я знаю, что этот вопрос старый, но я нашел его в 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()
.
Просто пища для размышлений для тех, кто это находит.
В современных выпусках добавлено $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" }
Это по умолчанию принимает последний элемент в массиве.