【问题标题】:Map reduce in RavenDb over 2 collections with child collection在 RavenDb 中使用子集合映射减少 2 个集合
【发布时间】:2012-05-11 03:27:32
【问题描述】:

我在 RavenDb 中存储了 2 种不同的对象类型,它们是父/子类型关系,就像 JSON 中的这样:

Account/1
{        
    "Name": "Acc1",
}

Items/1
{
    "Account": "Account/1",
    "Value" : "100",
    "Tags": [
       "tag1",
       "tag2"]
}

Items/2
{
    "Account": "Account/1",
    "Value" : "50",
    "Tags": [
       "tag2"]
}

请注意,我不想将这些存储在同一个文档中,因为一个帐户可能有数千个项目。

我正在尝试编写一个 map/reduce 索引,它将返回如下内容:

{
    "Account": "Acc1",
    "TagInfo": [
        { "TagName" : "tag1",
          "Count" : "1",  //Count of all the "tag1" occurrences for acc1
          "Value" : "100" //Sum of all the Values for acc1 which are tagged 'tag1'
        },
        { "TagName" : "tag2",
          "Count" : "2",  //Two items are tagged "tag2"
          "Value" : "150"
        }]
}

即所有不同标签名称的列表以及每个标签的数量及其值。

我认为我需要使用多映射将 Account 和 Items 集合映射在一起,但我无法弄清楚 reduce 部分来创建结果的“TagInfo”部分。

这可能吗,还是我在 Raven 中建模这一切都错了?

编辑:

我想从此查询中检索的类如下所示:

public class QueryResult
{
    public string AccountId {get;set;}
    public TagInfo Tags {get;set;} 
}

public class TagInfo
{
    public string TagName {get;set;}
    public int Count {get;set;}
    public int TotalSum {get;set;}
}

【问题讨论】:

    标签: c# mapreduce ravendb


    【解决方案1】:

    您不能为此使用 Multi Map/Reduce 索引,因为您希望标签上的一张地图和帐户上的另一张地图。它们没有共同的属性,所以你不能在这里有一个多映射/减少。

    但是,您可以改用 TransformResult。操作方法如下:

    public class Account
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }
    
    public class Item
    {
        public string Id { get; set; }
        public string AccountId { get; set; }
        public int Value { get; set; }
        public List<string> Tags { get; set; }
    }
    
    public class TagsWithCountAndValues : AbstractIndexCreationTask<Item, TagsWithCountAndValues.ReduceResult>
    {
        public class ReduceResult
        {
            public string AccountId { get; set; }
            public string AccountName { get; set; }
            public string Tag { get; set; }
            public int Count { get; set; }
            public int TotalSum { get; set; }
        }
    
        public TagsWithCountAndValues()
        {
            Map = items => from item in items
                            from tag in item.Tags
                            select new
                            {
                                AccountId = item.AccountId,
                                Tag = tag,
                                Count = 1,
                                TotalSum = item.Value
                            };
            Reduce = results => from result in results
                                group result by result.Tag
                                into g
                                select new
                                {
                                    AccountId = g.Select(x => x.AccountId).FirstOrDefault(),
                                    Tag = g.Key,
                                    Count = g.Sum(x => x.Count),
                                    TotalSum = g.Sum(x => x.TotalSum)
                                };
            TransformResults = (database, results) => from result in results
                                                        let account = database.Load<Account>(result.AccountId)
                                                        select new
                                                        {
                                                            AccountId = result.AccountId,
                                                            AccountName = account.Name,
                                                            Tag = result.Tag,
                                                            Count = result.Count,
                                                            TotalSum = result.TotalSum
                                                        };
        }
    }
    

    然后,你可以这样查询:

    var results = session.Query<TagsWithCountAndValues.ReduceResult, TagsWithCountAndValues>()
        .Where(x => x.AccountId == "accounts/1")                        
        .ToList();
    

    【讨论】:

    • 谢谢丹尼尔,我不知道TransformResults!不过,这并不是我想要的,我追求的是每个帐户的一个结果,其中包含一个包含标签详细信息的属性,请参阅我的编辑。此外,上面的 TotalSum 不起作用,因为它汇总了所有项目值,而不仅仅是我正在查询的帐户的值(我认为 reduce 需要根据帐户而不是标签进行分组?)
    • 您的结果中不能有一个包含嵌套类的索引。我不认为你可以在一个索引中得到你想要的。相反,我宁愿有一些独立的索引并查询它们以获取您需要的所有信息或更改数据模型以使其更适合该数据访问模式。
    • 好的,谢谢丹尼尔。我认为可能是这种情况,但不太确定。非常感谢您的宝贵时间。
    【解决方案2】:

    好的,所以我想出了一种基于 Daniel 回答的可接受方式来执行此操作的方法,因此我将在此处记录下来以供任何未来的旅行者(可能是我自己!)。

    我从尝试为每个帐户返回一个结果更改为每个帐户/标签组合一个结果,因此索引必须更改如下(注意 reduce 中的 group by 位于 2 个属性上):

    public class TagsWithCountAndValues : AbstractIndexCreationTask<Item, TagsWithCountAndValues.ReduceResult>
    {
        public class ReduceResult
        {
            public string AccountId { get; set; }
            public string AccountName { get; set; }
            public string TagName { get; set; }
            public int TagCount { get; set; }
            public int TagValue { get; set; }
        }
    
        public TagsWithCountAndValues()
        {
            Map = items => from item in items
                           from tag in item.Tags
                           select new ReduceResult
                           {
                               AccountId = item.AccountId,
                               TagName = tag,
                               TagCount = 1,
                               TagValue = item.Value
                           };
    
            Reduce = results => from result in results
                                where result.TagName != null
                                group result by new {result.AccountId, result.TagName}
                                into g
                                select new ReduceResult
                                           {
                                               AccountId = g.Key.AccountId,
                                               TagName = g.Key.TagName,
                                               TagCount = g.Sum(x => x.TagCount),
                                               TagValue = g.Sum(x => x.TagValue),
                                           };
    
            TransformResults = (database, results) => from result in results
                                                      let account = database.Load<Account>(result.AccountId)
                                                      select new ReduceResult
                                                                 {
                                                                     AccountId = result.AccountId,
                                                                     AccountName = account.Name,
                                                                     TagName = result.TagName,
                                                                     TagCount = result.TagCount,
                                                                     TagValue = result.TagValue,
                                                                 };
        }
    }
    

    和以前一样,查询这个只是:

    var results = session
        .Query<TagsWithCountAndValues.ReduceResult, TagsWithCountAndValues>()
        .ToList();
    

    然后可以通过内存中的 LINQ 查询将其结果转换为我最初想要的对象。此时可以返回的结果数量相对较少,因此在客户端执行此操作很容易接受。 LINQ 语句是:

    var hierachicalResult = from result in results
                            group new {result.TagName, result.TagValue} by result.AccountName
                            into g
                            select new
                            {
                                Account = g.Key,
                                TagInfo = g.Select(x => new { x.TagName, x.TagValue, x.TagCount })
                            };
    

    这为每个帐户提供了一个对象,以及一个 TagInfo 对象的子列表 - 每个唯一标签一个。

    【讨论】:

      猜你喜欢
      • 2011-12-12
      • 2012-05-30
      • 2016-01-18
      • 2015-08-02
      • 2012-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多