RavenDB - MapReduce复杂聚合

时间:2012-11-09 09:38:14

标签: .net linq mapreduce ravendb reduce

我有这样的文件:

 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" }

我想运行map-reduce查询,以返回按日期分组的结果列表,其中包含每个订单的员工事件计数。

在这种情况下,Mick在单个订单上的1/1上有2个事件。所有其他日子,员工在11月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 }

由于Mick是唯一一个在单个订单上的单个日期有多个事件的员工,因此结果只返回一个员工在一个日期的订单上有多个事件的计数。

在.NET中使用LINQ编写此map-reduce Raven查询的最佳方法是什么?

由于

1 个答案:

答案 0 :(得分: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,在决定每个员工的一天概念时,你还需要考虑更多。