RavenDB — сложное агрегирование MapReduce

У меня есть такой документ:

 order : 1
     event : { timestamp: 1/1/2012, employeeName: "mick" },
     event : { timestamp: 1/1/2012, employeeName: "mick" },
     event : { timestamp: 1/2/2012, employeeName: "rick" },
     event : { timestamp: 1/3/2012, employeeName: "mick" }

  order : 2
     event : { timestamp: 1/2/2012, employeeName: "mick" },
     event : { timestamp: 1/2/2012, employeeName: "rick" }

Я хочу запустить запрос на уменьшение карты, чтобы вернуть список результатов, сгруппированных по дате, с количеством событий сотрудников на заказ.

В данном случае у Мика было 2 события 1/1 в одном заказе. Все остальные дни было по одному событию со стороны сотрудника по каждому заказу 2 и 3 ноября. Поэтому мне нужна функция MAP с результатами, которые будут выглядеть так:

{ orderId: 1, date: 1/1/2012, employee: "mick", orderEventsCount: 2 },
{ orderId: 1, date: 1/2/2012, employee: "rick", orderEventsCount: 1 },
{ orderId: 2, date: 1/2/2012, employee: "mick", orderEventsCount: 1 },
{ orderId: 2, date: 1/2/2012, employee: "rick", orderEventsCount: 1 },
{ orderId: 1, date: 1/3/2012, employee: "mick", orderEventsCount: 1 }

Затем мне нужна функция REDUCE, которая будет принимать эти результаты и группировать только по дате и возвращать количество сотрудников в день с несколькими событиями в одном заказе:

{ date: 1/1/2012, multipleEventsPerOrdercount: 1 },
{ date: 1/2/2012, multipleEventsPerOrdercount: 0 },
{ date: 1/3/2012, multipleEventsPerOrdercount: 0 }

Поскольку Мик был единственным сотрудником, у которого было несколько событий в одну дату в одном заказе, результат вернул только количество одного сотрудника с несколькими событиями в заказе на дату.

Как лучше всего написать этот запрос Raven с уменьшением карты, используя LINQ в .NET?

Спасибо


person Faris Zacina    schedule 09.11.2012    source источник
comment
Это два отдельных документа заказа? Или один документ с двумя заказами в нем? Кроме того, даты никогда не попадут в документ так, как вы показали. Используете ли вы свойство DateTime в своем классе? Пожалуйста, покажите свой клиентский код или укажите, делаете ли вы прямые HTTP-вызовы. Спасибо.   -  person Matt Johnson-Pint    schedule 09.11.2012
comment
да. Это два отдельных документа заказа. Даты хранятся в документе RavenDB JSON в виде полей меток времени, например. 2012-11-08T02:32:13.5549981 и десериализован в поле Date в объектах классов Entity. Я не думаю, что код на стороне клиента здесь уместен, это чисто вычислительная проблема бэкэнда. Мне просто нужно чистое решение для уменьшения карты с использованием индексов RavenDB с использованием LINQ в .NET.   -  person Faris Zacina    schedule 09.11.2012


Ответы (1)


Предполагая, что ваши классы выглядят так:

public class Order
{
  public string Id  { get; set; }
  public List<Event> Events { get; set; }
}

public class Event
{
  public DateTime Timestamp { get; set; }
  public string EmployeeName { get; set; }
}

Тогда индекс, который вы запрашиваете, будет выглядеть так:

public class Orders_EventCountsByDate : 
    AbstractIndexCreationTask<Order, Orders_EventCountsByDate.Result>
{
  public class Result
  {
    public DateTime Date { get; set; }
    public double Count { get; set; }
  }

  public Orders_EventCountsByDate()
  {
    Map = orders => from order in orders
                    from evt in order.Events
                    let subtotal = order.Events.Count(x => x.EmployeeName == evt.EmployeeName && x.Timestamp == evt.Timestamp)
                    select new
                    {
                      evt.Timestamp.Date,
                      Count = subtotal > 1 ? (1.0 / subtotal) : 0
                    };

    Reduce = results => from result in results
                        group result by result.Date
                        into g
                        select new
                        {
                          Date = g.Key,
                          Count = g.Sum(x => x.Count)
                        };
  }
}

И вы бы использовали его так:

var counts = session.Query<Orders_EventCountsByDate.Result,
                           Orders_EventCountsByDate>();

Хитрость здесь заключается в том, что вы определяете на Карте, сколько вы хотите, чтобы каждое событие способствовало подсчету. Если есть только одно событие, вы вносите ноль. При наличии нескольких событий каждое событие вносит долю в общее количество. Эти дроби позже суммируются в уменьшении, возвращая вас почти к целым числам. Двойная математика с плавающей запятой должна вернуть вас к целым числам, но вы все равно можете захотеть округлить до ближайшего целого числа в коде на стороне клиента, чтобы быть в безопасности.

Это также предполагает, что все события происходят в одном и том же часовом поясе, и вас не волнуют изменения летнего времени или время указано в формате UTC. Если ни то, ни другое, то вам следует использовать DateTimeOffset, и вам нужно больше учитывать при принятии решения о том, что представляет собой концепция дня каждого сотрудника.

person Matt Johnson-Pint    schedule 09.11.2012
comment
Извините, я только что перечитал ваш вопрос и понял, что вы искали не простой подсчет по дате, а тот, который подсчитывает только сотрудников с более чем одним заказом на одну и ту же дату. В ближайшее время у меня будет обновленный ответ. - person Matt Johnson-Pint; 09.11.2012
comment
Хорошо, я обновил ответ, чтобы он соответствовал вашему вопросу. Как видите, здесь задействована некоторая математика, поэтому, вероятно, ответ был неуловимым. - person Matt Johnson-Pint; 09.11.2012