Используя $slice operator для получения последнего элемента массива в mongodb

Как получить последний элемент массива с условиями в mongodb. Я не могу использовать срез. Вот мой код

{ "1" : { "relevancy" : [  "Y" ] }, "_id" : ObjectId("530824b95f44eac1068b45c0") }
{ "1" : { "relevancy" : [  "Y",  "Y" ] }, "_id" : ObjectId("530824b95f44eac1068b45c2") }
{ "1" : { "relevancy" : [  "N" ] }, "_id" : ObjectId("530824b95f44eac1068b45c3") }
{ "1" : { "relevancy" : [  "Y",  "Y" ] }, "_id" : ObjectId("530824b95f44eac1068b45c4") }
{ "1" : { "relevancy" : [  "Y",  "N" ] }, "_id" : ObjectId("530824b95f44eac1068b45c6") }
{ "1" : { "relevancy" : [  "N" ] }, "_id" : ObjectId("530824b95f44eac1068b45c7") }
{ "1" : { "relevancy" : [  "Y",  "N" ] }, "_id" : ObjectId("530824b95f44eac1068b45c8") }

Я хочу подсчитать количество строк, имеющих "Y" в качестве последнего элемента массива "релевантность". Из кода выше должно быть 3. Как это сделать. Пожалуйста, помогите мне.

Ответ 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.