MongoDB – Запрос к последнему элементу массива?

Я знаю, что MongoDB поддерживает синтаксис find{array.0.field:"value"}, но я специально хочу сделать это для последнего элемента в массиве, а это значит, что я не знаю индекс. Есть какой-то оператор для этого, или мне не повезло?

РЕДАКТИРОВАТЬ: Чтобы уточнить, я хочу, чтобы find() возвращал только документы, в которых поле в последнем элементе массива соответствует определенному значению.


person Joseph Blair    schedule 23.02.2015    source источник
comment
Как выглядят ваши документы?   -  person Juan Carlos Farah    schedule 23.02.2015
comment
Ну, массив, который я пытаюсь протестировать, на самом деле вложен в другой массив, но я не думаю, что это должно иметь какой-либо эффект. По сути, я хочу, чтобы в моем селекторе поиска возвращались только документы, в которых определенное поле в последнем элементе массива соответствует определенному значению.   -  person Joseph Blair    schedule 23.02.2015
comment
Вы должны изучить агрегацию, а затем поиграть с $unwind, $project, $match, и $group   -  person Howard Lee    schedule 23.02.2015
comment
С новейшей MongoDB вы можете сделать это: find({"array.-1.field":"value"})   -  person Mars Lee    schedule 16.04.2019
comment
@MarsLee, у меня это не работает в версии 4.2.0 - у вас есть дополнительная информация об этом?   -  person Chrift    schedule 08.11.2019


Ответы (10)


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

db.collection.aggregate([
   { $project: { id: 1, myField: { $slice: [ "$myField", -1 ] } } },
   { $match: { myField: "myValue" } }
]);
person jdeyrup    schedule 18.10.2016
comment
в 3.2 есть оператор $arrayElemAt, который работает с отрицательными индексами. так что это может быть { $project: { id: 1, myField: { $arrayElemAt: [ "$myField", -1 ] } } }. Я не знаю, так ли эффективен срез с одним элементом - person makhdumi; 29.11.2016
comment
Это выводит именно то, что хотел вопрос. - person Zachary Ryan Smith; 13.10.2017
comment
Начиная с MongoDB версии 3.4, также существует оператор $addFields, который означает, что вам больше не нужно проецировать отдельные поля. - person Wan Bachtiar; 11.01.2018

Вы можете использовать $expr (оператор версии 3.6 mongo) для использования функции агрегации в обычном запросе.

Сравните query operators с aggregation comparison operators.

Для скалярных массивов

db.col.find({$expr: {$gt: [{$arrayElemAt: ["$array", -1]}, value]}})

Для встроенных массивов — используйте выражение $arrayElemAt с точечной нотацией для проецирования последним элемент.

db.col.find({$expr: {$gt: [{"$arrayElemAt": ["$array.field", -1]}, value]}})

Код пружины @Query

@Query("{$expr:{$gt:[{$arrayElemAt:[\"$array\", -1]}, ?0]}}")
ReturnType MethodName(ArgType arg);
person s7vr    schedule 23.01.2018
comment
Для встроенных массивов, почему вы используете 0, а не -1? - person Wrong; 30.08.2019
comment
Это решение сработало для меня. Я использовал встроенные массивы, чтобы проверить, меньше ли дата внутри самого массива, чем текущая дата. И если это меньше, чем я обновляю документ. const query = { $expr: { $lt: [{ $arrayElemAt: ["$lessons.studentData.dueDate", -1] }, new Date()] } }; const update = { completed: true }; Class.updateMany(query, update) - person Snoopy; 14.11.2019
comment
db.col.find({$expr: {$gt: [{$arrayElemAt: ["array", -1]}, value]}}) выдал мне ошибку. --› db.col.find({$expr: {$gt: [{$arrayElemAt: ["$array", -1]}, value]}}) - person Patrick DaVader; 13.07.2020

используйте $slice.

db.collection.find( {}, { array_field: { $slice: -1 } } )

Редактирование: Вы можете использовать { <field>: { $elemMatch: { <query1>, <query2>, ... } } }, чтобы найти совпадение.

Но это не даст именно то, что вы ищете. Я не думаю, что это возможно в mongoDB.

person Kaushal    schedule 23.02.2015
comment
Это просто возвращает последний элемент, верно? Это не то, что я пытаюсь сделать. Я хочу, чтобы мой селектор поиска возвращал только документы, в которых последний элемент массива соответствует определенному значению. - person Joseph Blair; 23.02.2015
comment
Просто имейте в виду, что '$slice' возвращает массив, а '$arrayElemAt' возвращает документ (тот же синтаксис). - person 0zkr PM; 23.08.2018

Я разместил сообщение в официальной группе Google Mongo здесь и получил ответ от их сотрудников. Похоже, то, что я ищу, невозможно. Я собираюсь просто использовать другой подход к схеме.

person Joseph Blair    schedule 23.02.2015

Версия 3.6 использует агрегацию для достижения того же.

db.getCollection('deviceTrackerHistory').aggregate([
   {
     $match:{clientId:"12"}
   },
   {
     $project:
      {
         deviceId:1,
         recent: { $arrayElemAt: [ "$history", -1 ] }
      }
   }
])
person KARTHIKEYAN.A    schedule 16.02.2018

Начиная с Mongo 4.4 можно использовать оператор агрегации $last. для доступа к последнему элементу массива:


Например, в запросе find:

// { "myArray": ["A", "B", "C"] }
// { "myArray": ["D"] }
db.collection.find({ $expr: { $eq: [{ $last: "$myArray" }, "C"] } })
// { "myArray": ["A", "B", "C"] }

Или в запросе aggregation:

db.collection.aggregate([
  { $addFields: { last: { $last: "$myArray" } } },
  { $match: { last: "C" } }
])
person Xavier Guihot    schedule 24.01.2020
comment
Это правильный ответ в 2021 году! - person Shadoweb; 21.03.2021

Вы можете использовать $position: 0 всякий раз, когда вы $push, а затем всегда запрашивать array.0 для получить последний добавленный элемент. Конечно, тогда вы не сможете получить новый «последний» элемент.

person Ricky Sahu    schedule 18.06.2015
comment
Я думал об этом, но мое приложение обращается к Mongo с помощью Spring Data, который в то время не поддерживал перемещение в начало массива. Не уверен, что сейчас. - person Joseph Blair; 10.08.2015

Не уверен насчет производительности, но это хорошо работает для меня:

db.getCollection('test').find(
  {
    $where: "this.someArray[this.someArray.length - 1] === 'pattern'"
  }
)
person Ramil Muratov    schedule 02.12.2015
comment
$where очень медленный, потому что он запускает javascript - person eagleeye; 20.07.2017
comment
Большое спасибо! Возможно, это не самое эффективное решение, но для меня это работает как шарм. Не так медленно для моих данных, возможно, эта разница в производительности заметна только в больших наборах данных. - person Leo Cavalcante; 18.01.2018
comment
плохая практика @eagleeye. - person Janen R; 09.11.2018

Вы можете решить это с помощью агрегации.

model.aggregate([
    {
      $addFields: {
        lastArrayElement: {
          $slice: ["$array", -1],
        },
      },
    },
    {
      $match: {
        "lastArrayElement.field": value,
      },
    },
  ]);

Быстрые объяснения. aggregate создает конвейер действий, выполняемых последовательно, поэтому в качестве параметра принимает массив. Сначала мы используем этап конвейера $addFields. Это новое в версии 3.4 и в основном означает: Сохранить все существующие поля документа, но также добавить следующие. В нашем случае мы добавляем lastArrayElement и определяем его как последний элемент в массиве с именем array. Далее мы выполняем этап конвейера $match. Входными данными для этого являются выходные данные предыдущего этапа, которые включают наше новое поле lastArrayElement. Здесь мы говорим, что мы включаем только те документы, где его поле field имеет значение value.

Обратите внимание, что результирующие совпадающие документы будут включать lastArrayElement. Если по какой-то причине вы действительно этого не хотите, вы можете добавить этап конвейера $project после $match, чтобы удалить его.

person Jonas Rosenqvist    schedule 25.01.2021

Для ответа используйте $arrayElemAt, если я хочу номер заказа: «12345» и значение последнего элемента $ gt, чем «значение»? как сделать $expr? Благодарность!

Для встроенных массивов — используйте выражение $arrayElemAt с записью через точку, чтобы проецировать последний элемент.

 db.col.find({$expr: {$gt: [{"$arrayElemAt": ["$array.field", -1]}, value]}})
person Robin Jiao    schedule 04.12.2019
comment
Спасибо за редактирование; я пробовал несколько раз и думаю, что у меня получилось: ``` db.col.find({$expr:{$and:[{$gt: [{$arrayElemAt: [$array.field, -1]} , значение]},{$eq:[{$orderNumber},12345]} ] }}) ``` - person Robin Jiao; 04.12.2019