MongoDB: получить последний полный документ для каждого идентификатора по дате / времени

Мне нужно получить последние документы, которые находятся в массиве идентификаторов на основе данных / времени. У меня есть следующий запрос, который делает это, но он возвращает только поля _id и acquiredTime. Как я могу заставить его вернуть полный документ со всеми полями?

db.trip.aggregate([
   { $match: { tripId: { $in: ["trip01", "trip02" ]}} },
   { $sort: { acquiredTime: -1} },
   { $group: { _id: "$tripId" , acquiredTime: { $first: "$acquiredTime" }}} 
])

Коллекция выглядит примерно так:

[{
   "tripId": "trip01",
   "acquiredTime": 1000,
   "name": "abc",
   "value": "abc"
},{
   "tripId": "trip02",
   "acquiredTime": 1000,
   "name": "xyz",
   "value": "xyz"
},{
   "tripId": "trip01",
   "acquiredTime": 2000,
   "name": "def",
   "value": "abc"
},{
   "tripId": "trip02",
   "acquiredTime": 2000,
   "name": "ghi",
   "value": "xyz"
}]

На данный момент получаю:

[{
   "tripId": "trip01",
   "acquiredTime": 2000
},{
   "tripId": "trip02",
   "acquiredTime": 2000
}]

Мне нужно получить:

[{
   "tripId": "trip01",
   "acquiredTime": 2000,
   "name": "def",
   "value": "abc"
},{
   "tripId": "trip02",
   "acquiredTime": 2000,
   "name": "ghi",
   "value": "xyz"
}]

person Jerry    schedule 22.01.2015    source источник
comment
Поскольку все поля из одной коллекции, я не думаю, что требуется агрегирование, простой поиск будет работать.   -  person Naresh kumar    schedule 22.01.2015
comment
@Nareshkumar При чем тут несколько коллекций? MongoDB ничего не делает с несколькими коллекциями. Если вы не понимаете, зачем здесь нужен агрегат, значит, вы неправильно прочитали вопрос.   -  person Neil Lunn    schedule 22.01.2015
comment
Извините за это, я не понял контекста использования рассматриваемого агрегата.   -  person Naresh kumar    schedule 22.01.2015


Ответы (2)


Ваш подход - правильный подход, но дело в том, что _1 _ и $project Просто не работайте так и требуйте, чтобы вы указали все поля, которые вы хотите получить в результате.

Если вы не против, чтобы структура выглядела немного иначе, вы всегда можете использовать $$ROOT в MongoDB версии 2.6 и выше:

db.trip.aggregate([
   { "$match": { "tripId": { "$in": ["trip01", "trip02" ]}} },
   { "$sort": { "acquiredTime": -1} },
   { "$group": { "_id": "$tripId" , "doc": { "$first": "$$ROOT" }}} 
])

Таким образом, весь документ присутствует, но только он содержится в качестве субдокумента для «doc» в результатах.

Для чего-то еще или более красивого вам нужно будет указать каждое поле, которое вы хотите. Это просто структура данных, поэтому вы всегда можете сгенерировать ее из кода.

db.trip.aggregate([
   { "$match": { "tripId": { "$in": ["trip01", "trip02" ]}} },
   { "$sort": { "acquiredTime": -1} },
   { "$group": { 
       "_id": "$tripId" , 
       "acquiredTime": { "$first": "$acquiredTime" },
       "name": { "$first": "$name" },
       "value": { "$first": "$value" }
   }}
])
person Neil Lunn    schedule 22.01.2015

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

Ссылка: https://docs.mongodb.com/manual/tutorial/sort-results-with-indexes/

Чтобы максимизировать производительность и минимизировать использование оперативной памяти:

  • Создайте уникальный индекс [(tripId, 1), (acquiredTime, -1)]
  • Сортировка должна работать точно по индексу

Это конечно будет стоить вам индекса, который будет тормозить вставки - бесплатного питания нет :)

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

db.trip.aggregate([
   { "$match": { "tripId": { "$in": ["trip01", "trip02" ]}} },
   { "$sort": SON([("tripId", 1), ("acquiredTime", -1)],
   { "$group": { "_id": "$tripId" , "doc": { "$first": "$$ROOT" }}},
   { "$replaceRoot": { "newRoot": "$doc"}} 
])

И, наконец, стоит отметить, что, если приобретенное время - это просто время вашего сервера, вы можете избавиться от него, поскольку _id уже включает временную метку создания. Таким образом, уникальный индекс будет продолжаться [(tripId, 1), (_id, -1)], и запрос будет выглядеть следующим образом:

db.trip.aggregate([
   { "$match": { "tripId": { "$in": ["trip01", "trip02" ]}} },
   { "$sort": SON([("tripId", 1), ("_id", -1)],
   { "$group": { "_id": "$tripId" , "doc": { "$first": "$$ROOT" }}},
   { "$replaceRoot": { "newRoot": "$doc"}} 
])

Это также лучше, поскольку объекты даты в MongoDB имеют разрешение 1 миллисекунду, что - в зависимости от частоты ваших вставок - может привести к чрезвычайно трудному воспроизведению условий гонки, тогда как автоматически сгенерированные _id гарантированно будут строго инкрементными.

person crusaderky    schedule 19.03.2019