Для каждого документа получить объект с полем $ max из массива. - PullRequest
0 голосов
/ 02 мая 2018

В моей коллекции есть следующие документы. Каждый документ содержит исторические погодные данные о конкретном месте:

{
'location':'new york', 
'history':[
    {'timestamp':1524542400, 'temp':79, 'wind_speed':1, 'wind_direction':'SW'}
    {'timestamp':1524548400, 'temp':80, 'wind_speed':2, 'wind_direction':'SW'}
    {'timestamp':1524554400, 'temp':82, 'wind_speed':3, 'wind_direction':'S'}
    {'timestamp':1524560400, 'temp':78, 'wind_speed':4, 'wind_direction':'S'}
    ]
},
{
'location':'san francisco', 
'history':[
    {'timestamp':1524542400, 'temp':80, 'wind_speed':5, 'wind_direction':'SW'}
    {'timestamp':1524548400, 'temp':81, 'wind_speed':6, 'wind_direction':'SW'}
    {'timestamp':1524554400, 'temp':82, 'wind_speed':7, 'wind_direction':'S'}
    {'timestamp':1524560400, 'temp':73, 'wind_speed':8, 'wind_direction':'S'}
    ]
},
{
'location':'miami', 
'history':[
    {'timestamp':1524542400, 'temp':84, 'wind_speed':9, 'wind_direction':'SW'}
    {'timestamp':1524548400, 'temp':85, 'wind_speed':10, 'wind_direction':'SW'}
    {'timestamp':1524554400, 'temp':86, 'wind_speed':11, 'wind_direction':'S'}
    {'timestamp':1524560400, 'temp':87, 'wind_speed':12, 'wind_direction':'S'}
    ]
}

Я хотел бы получить список самых последних данных о погоде для каждого местоположения (более или менее), например, так:

{
'location':'new york', 
'history':{'timestamp':1524560400, 'temp':78, 'wind_speed':4, 'wind_direction':'S'}
},
{
'location':'san francisco', 
'history':{'timestamp':1524560400, 'temp':73, 'wind_speed':8, 'wind_direction':'S'}
},
{
'location':'miami', 
'history':{'timestamp':1524560400, 'temp':87, 'wind_speed':12, 'wind_direction':'S'}
}

Я был почти уверен, что нужен какой-то агрегат $ group, но не могу понять, как выбрать весь объект по $max:<field>. Например, приведенный ниже запрос возвращает только саму максимальную метку времени без каких-либо сопутствующих полей.

db.collection.aggregate([{
    '$unwind': '$history'
}, {
    '$group': {
        '_id': '$name',
        'timestamp': {
            '$max': '$history.timestamp'
        }
    }
}])

возвращает

{ "_id" : "new york", "timestamp" : 1524560400 }
{ "_id" : "san franciscoeo", "timestamp" : 1524560400 }
{ "_id" : "miami", "timestamp" : 1524560400 }

Фактическая коллекция и массивы очень большие, поэтому обработка на стороне клиента не будет идеальной. Любая помощь будет высоко ценится.

1 Ответ

0 голосов
/ 02 мая 2018

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

Одно совпадение на документ

Короче говоря, мы можем применить $max к вашему конкретному случаю, используемому с $indexOfArray и $arrayElemAt для извлечения соответствующего значения:

db.collection.aggregate([
  { "$addFields": {
    "history": {
      "$arrayElemAt": [
        "$history",
        { "$indexOfArray": [ "$history.timestamp", { "$max": "$history.timestamp" } ] }
      ]
    }
  }}
])

Который вернет вам:

{
        "_id" : ObjectId("5ae9175564de8a00a66b3974"),
        "location" : "new york",
        "history" : {
                "timestamp" : 1524560400,
                "temp" : 78,
                "wind_speed" : 4,
                "wind_direction" : "S"
        }
}
{
        "_id" : ObjectId("5ae9175564de8a00a66b3975"),
        "location" : "san francisco",
        "history" : {
                "timestamp" : 1524560400,
                "temp" : 73,
                "wind_speed" : 8,
                "wind_direction" : "S"
        }
}
{
        "_id" : ObjectId("5ae9175564de8a00a66b3976"),
        "location" : "miami",
        "history" : {
                "timestamp" : 1524560400,
                "temp" : 87,
                "wind_speed" : 12,
                "wind_direction" : "S"
        }
}

Это, разумеется, без необходимости «группировать» что-либо и просто найти значение $max в каждом документе, как вы, похоже, пытаетесь это сделать. Это избавляет от необходимости «манипулировать» любым другим документом, выдавая его через $group или действительно $unwind.

По сути, использование заключается в том, что $max возвращает «максимальное» значение из указанного свойства массива, поскольку $history.timestamp - это короткий способ извлечения «только этих значений» из объектов массива.

Используется по сравнению с тем же «списком значений», чтобы определить соответствующий «индекс» с помощью $indexOfArray, который принимает массив в качестве первого аргумента и значение для сопоставления в качестве второго .

Оператор $arrayElemAt также принимает массив в качестве первого аргумента, здесь мы используем полный массив "$history", так как мы хотим извлечь «полный объект». Что мы делаем с помощью значения «возвращаемого индекса» оператора $indexOfArray.

«Несколько» совпадений на документ

Конечно, это хорошо для «одиночных» совпадений, но если вы хотите расширить это значение до «множественных» совпадений с одинаковым значением $max, то вы должны использовать $filter вместо:

db.collection.aggregate([
  { "$addFields": {
    "history": {
      "$filter": {
        "input": "$history",
        "cond": { "$eq": [ "$$this.timestamp", { "$max": "$history.timestamp" } ] }
      }
    }
  }}
])

Что бы вывести:

{
        "_id" : ObjectId("5ae9175564de8a00a66b3974"),
        "location" : "new york",
        "history" : [
                {
                        "timestamp" : 1524560400,
                        "temp" : 78,
                        "wind_speed" : 4,
                        "wind_direction" : "S"
                }
        ]
}
{
        "_id" : ObjectId("5ae9175564de8a00a66b3975"),
        "location" : "san francisco",
        "history" : [
                {
                        "timestamp" : 1524560400,
                        "temp" : 73,
                        "wind_speed" : 8,
                        "wind_direction" : "S"
                }
        ]
}
{
        "_id" : ObjectId("5ae9175564de8a00a66b3976"),
        "location" : "miami",
        "history" : [
                {
                        "timestamp" : 1524560400,
                        "temp" : 87,
                        "wind_speed" : 12,
                        "wind_direction" : "S"
                }
        ]
}

Основное различие заключается в том, что свойство "history" по-прежнему является "массивом", поскольку именно это будет генерировать $filter. Также, конечно, отметив, что если бы на самом деле существовали «множественные» записи с одинаковым значением метки времени, то это, конечно, вернуло бы их всех, а не только «первый индекс».

Сравнение в основном выполняется вместо каждого элемента массива, чтобы определить, имеет ли объект current ("$$this") указанное свойство, соответствующее результату $max, и в конечном итоге возвращает только те элементы массива, которые соответствуют заданному условию.


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

Если, однако, вам действительно нужно $group по «нескольким документам» по определенному ключу группировки и учету значений «внутри» массива, то первоначальный подход, обрисованный в общих чертах, как вы обнаружили, на самом деле подходит для этого сценария, как и в конечном итоге, вы «должны» $unwind, чтобы обрабатывать элементы «внутри» массива таким образом. А также с учетом «по всем документам».

Так что будьте внимательны, используя такие этапы, как $group и $unwind только там, где вам действительно нужно и где «группировка» - это ваше реальное намерение , Если вы просто ищете что-то «в документе», то есть гораздо более эффективные способы сделать это без дополнительных затрат, которые эти этапы приносят с собой на обработку.

...