Ответ 1
Как вы уже знаете, $slice используется только в проекции, чтобы ограничить элементы массива, возвращаемые в результатах. Таким образом, вы будете зависеть от обработки списка программным путем с помощью результатов find().
Лучше использовать aggregate. Но сначала рассмотрим, как используется $slice:
> db.collection.find({},{ relevancy: {$slice: -1} })
{ "_id" : ObjectId("530824b95f44eac1068b45c0"), "relevancy" : [ "Y" ] }
{ "_id" : ObjectId("530824b95f44eac1068b45c2"), "relevancy" : [ "Y" ] }
{ "_id" : ObjectId("530824b95f44eac1068b45c3"), "relevancy" : [ "N" ] }
{ "_id" : ObjectId("530824b95f44eac1068b45c4"), "relevancy" : [ "Y" ] }
{ "_id" : ObjectId("530824b95f44eac1068b45c6"), "relevancy" : [ "N" ] }
{ "_id" : ObjectId("530824b95f44eac1068b45c7"), "relevancy" : [ "N" ] }
{ "_id" : ObjectId("530824b95f44eac1068b45c8"), "relevancy" : [ "N" ] }
Итак, вы получаете последний элемент массива, но вы зацикливаете результаты цикла, поскольку вы не можете сопоставить последнее значение элемента. Вы могли бы просто сделать это в коде.
Теперь посмотрим на aggregate:
db.collection.aggregate([
// Match things so we get rid of the documents that will never match, but it will
// still keep some of course since they are arrays, that *may* contain "N"
{ "$match": { "relevancy": "Y" } },
// De-normalizes the array
{ "$unwind": "$relevancy" },
// The order of the array is retained, so just look for the $last by _id
{ "$group": { "_id": "$_id", "relevancy": { "$last": "$relevancy" } }},
// Match only the records with the results you want
{ "$match": { "relevancy": "Y" }},
// Oh, and maintain the original _id order [ funny thing about $last ]
{ "$sort": { "_id": 1 } }
])
Даже если это будет ваше первое использование aggregate(), я рекомендую вам узнать его. Это, пожалуй, самый полезный инструмент для решения проблем. Конечно, для меня. Поместите каждый шаг один раз за раз, если вы учитесь.
Также не уверен в вашей форме документа, все нотации под-документа 1: { ... }
представляется ошибкой, но вы должны очистить это или скорректировать выше код для ссылки "1.relevancy"
. Я надеюсь, что ваши документы на самом деле больше похожи на это:
{ "relevancy" : [ "Y" ] , "_id" : ObjectId("530824b95f44eac1068b45c0") }
{ "relevancy" : [ "Y", "Y" ] , "_id" : ObjectId("530824b95f44eac1068b45c2") }
{ "relevancy" : [ "N" ], "_id" : ObjectId("530824b95f44eac1068b45c3") }
{ "relevancy" : [ "Y", "Y" ], "_id" : ObjectId("530824b95f44eac1068b45c4") }
{ "relevancy" : [ "Y", "N" ], "_id" : ObjectId("530824b95f44eac1068b45c6") }
{ "relevancy" : [ "N" ], "_id" : ObjectId("530824b95f44eac1068b45c7") }
{ "relevancy" : [ "Y", "N" ], "_id" : ObjectId("530824b95f44eac1068b45c8") }
MongoDB 3.2.x и вверх
Конечно, MongoDB 3.2 вводит оператор "агрегации" для $slice
и еще лучше $arrayElemAt
, который устраняет необходимость в обработке $unwind
и $group
. После первоначального запроса $match
вы просто делаете "логическое соответствие" с $redact
:
db.collection.aggregate([
{ "$match": { "relevancy": "Y" } },
{ "$redact": {
"$cond": {
"if": { "$eq": [{ "$arrayElemAt": [ "$relevancy", -1 ], "Y" ] },
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
Это будет делать проверку на последнем элементе массива при принятии решения о том, следует ли $$KEEP
или $$PRUNE
документы из возвращенных результатов.
Если вы все еще хотели "проекцию", вы можете фактически добавить $slice
:
db.collection.aggregate([
{ "$match": { "relevancy": "Y" } },
{ "$redact": {
"$cond": {
"if": { "$eq": [{ "$arrayElemAt": [ "$relevancy", -1 ], "Y" ] },
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
{ "$project": { "relevancy": { "$slice": [ "$relevancy", -1 ] } } }
])
Или альтернативный подход:
db.collection.aggregate([
{ "$match": { "relevancy": "Y" } },
{ "$project": { "relevancy": { "$slice": [ "$relevancy", -1 ] } } },
{ "$match": { "relevancy": "Y" } }
])
Но, вероятно, менее дорого, чтобы сначала сделать $redact
и "затем" сделать любое изменение в `$ project.