Подтвердить что ты не робот

MongoDB агрегат в пределах ежедневной группировки

У меня есть некоторые документы в манго, которые выглядят примерно так:

{
  _id : ObjectId("..."),
  "make" : "Nissan",
  ..
},
{
  _id : ObjectId("..."),
  "make" : "Nissan",
  "saleDate" :  ISODate("2013-04-10T12:39:50.676Z"),
  ..
}

В идеале я хотел бы иметь возможность подсчитать количество продаваемых автомобилей в день. Затем мне хотелось бы посмотреть или сегодня, или окно, такое как сегодня, через последние семь дней.

Я смог выполнить ежедневный просмотр с помощью некоторого уродливого кода

db.inventory.aggregate(
  { $match : { "saleDate" : { $gte: ISODate("2013-04-10T00:00:00.000Z"), $lt: ISODate("2013-04-11T00:00:00.000Z")  } } } ,
  { $group : { _id : { make : "$make", saleDayOfMonth : { $dayOfMonth : "$saleDate" } }, cnt : { $sum : 1 } } }
)

Что дает результаты

{
  "result" : [
    {
      "_id" : {
        "make" : "Nissan",
        "saleDayOfMonth" : 10
      },
      "cnt" : 2
    },
    {
      "_id" : {
        "make" : "Toyota",
        "saleDayOfMonth" : 10
      },
      "cnt" : 4
    },
  ],
  "ok" : 1
}

Итак, это нормально, но я бы предпочел не менять два значения datetime в запросе. Затем, как я уже упоминал выше, я хотел бы иметь возможность запускать этот запрос (опять же, без необходимости изменять его каждый раз) и видеть те же результаты, которые были сведены к концу дня за последнюю неделю.

О, и вот примеры данных, которые я использовал для запроса

db.inventory.save({"make" : "Nissan","saleDate" :  ISODate("2013-04-10T12:39:50.676Z")});
db.inventory.save({"make" : "Nissan"});
db.inventory.save({"make" : "Nissan","saleDate" :  ISODate("2013-04-10T11:39:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" :  ISODate("2013-04-09T11:39:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" :  ISODate("2013-04-10T11:38:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" :  ISODate("2013-04-10T11:37:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" :  ISODate("2013-04-10T11:36:50.676Z")});
db.inventory.save({"make" : "Toyota","saleDate" :  ISODate("2013-04-10T11:35:50.676Z")});

Спасибо заранее, Кевин

4b9b3361

Ответ 1

В Mongo 2.8 RC2 появился новый оператор агрегации данных: $dateToString, который можно использовать для группировки по дням и просто иметь "ГГГГ-ММ-ДД" в результате:

Пример из документации:

db.sales.aggregate(
  [
     {
         $project: {
                yearMonthDay: { $dateToString: { format: "%Y-%m-%d", date: "$date" } },
                time: { $dateToString: { format: "%H:%M:%S:%L", date: "$date" } }
         }
     }
  ]
)

приведет к:

{ "_id" : 1, "yearMonthDay" : "2014-01-01", "time" : "08:15:39:736" }

Ответ 2

Возможно, вы захотите взглянуть на мою запись в блоге о том, как справиться с различными манипуляциями с датами в структуре агрегации здесь.

Что вы можете сделать, это использовать фазу $project, чтобы урезать даты до ежедневного разрешения, а затем запустить агрегацию по всему набору данных (или только его часть) и агрегировать по дате и сделать.

С вашими примерными данными, скажем, вы хотите узнать, сколько автомобилей вы продали по маркам, по дате в этом году:

match={"$match" : {
               "saleDate" : { "$gt" : new Date(2013,0,1) }
      }
};

proj1={"$project" : {
        "_id" : 0,
        "saleDate" : 1,
        "make" : 1,
        "h" : {
            "$hour" : "$saleDate"
        },
        "m" : {
            "$minute" : "$saleDate"
        },
        "s" : {
            "$second" : "$saleDate"
        },
        "ml" : {
            "$millisecond" : "$saleDate"
        }
    }
};

proj2={"$project" : {
        "_id" : 0,
        "make" : 1,
        "saleDate" : {
            "$subtract" : [
                "$saleDate",
                {
                    "$add" : [
                        "$ml",
                        {
                            "$multiply" : [
                                "$s",
                                1000
                            ]
                        },
                        {
                            "$multiply" : [
                                "$m",
                                60,
                                1000
                            ]
                        },
                        {
                            "$multiply" : [
                                "$h",
                                60,
                                60,
                                1000
                            ]
                        }
                    ]
                }
            ]
        }
    }
};

group={"$group" : {
        "_id" : {
            "m" : "$make",
            "d" : "$saleDate"
        },
        "count" : {
            "$sum" : 1
        }
    }
};

Теперь запуск агрегации дает вам:

db.inventory.aggregate(match, proj1, proj2, group)
{
    "result" : [
        {
            "_id" : {
                "m" : "Toyota",
                "d" : ISODate("2013-04-10T00:00:00Z")
            },
            "count" : 4
        },
        {
            "_id" : {
                "m" : "Toyota",
                "d" : ISODate("2013-04-09T00:00:00Z")
            },
            "count" : 1
        },
        {
            "_id" : {
                "m" : "Nissan",
                "d" : ISODate("2013-04-10T00:00:00Z")
            },
            "count" : 2
        }
    ],
    "ok" : 1
}

Вы можете добавить еще одну фазу {$ project}, чтобы добавить результат, и вы можете добавить шаг {$ sort}, но в основном для каждой даты, для каждого из них вы получите счет того, сколько было продано.

Ответ 3

Мне нравится user1083621, но этот метод вызывает некоторые ограничения в следующих операциях с этим полем - потому что вы не можете использовать его как поле даты в (например) следующем агрегации. Вы не можете ни сравнивать, ни использовать операции агрегации дат, и после агрегации вы будете иметь строки (!). Все это можно решить, проецируя исходное поле даты, но в этом случае вы столкнетесь с некоторыми трудностями, сохранив его на этапе групповой работы. И в конце концов, иногда вы просто хотите манипулировать с началом дня, а не с произвольным дневным временем. Итак, вот мой метод:

{'$project': {
    'start_of_day': {'$subtract': [
        '$date',
        {'$add': [
            {'$multiply': [{'$hour': '$date'}, 3600000]},
            {'$multiply': [{'$minute': '$date'}, 60000]},
            {'$multiply': [{'$second': '$date'}, 1000]},
            {'$millisecond': '$date'}
        ]}
    ]},
}}

Это дает вам следующее:

{
    "start_of_day" : ISODate("2015-12-03T00:00:00.000Z")
},
{
    "start_of_day" : ISODate("2015-12-04T00:00:00.000Z")
}

Невозможно сказать, если он быстрее, чем user1083621.