【问题标题】:How to do a cross join / cartesian product in RavenDB?如何在 RavenDB 中进行交叉连接/笛卡尔积?
【发布时间】:2023-03-23 18:28:01
【问题描述】:

我有一个在后端使用 RavenDB 并允许用户跟踪库存的 Web 应用程序。我域中的三个实体是:

public class Location
{
    string Id
    string Name 
}

public class ItemType
{
    string Id
    string Name
}

public class Item
{
    string Id
    DenormalizedRef<Location> Location
    DenormalizedRef<ItemType> ItemType
}

在我的网络应用程序中,有一个页面供用户查看他们在各个位置的库存明细。具体来说,它会显示位置名称、项目类型名称以及项目数量。

我采用的第一种方法是在 InventoryItems 上创建 map/reduce 索引:

this.Map = inventoryItems =>
    from inventoryItem in inventoryItems
    select new
    {
        LocationName = inventoryItem.Location.Name,
        ItemTypeName = inventoryItem.ItemType.Name,
        Count = 1
    });

this.Reduce = indexEntries =>
    from indexEntry in indexEntries
    group indexEntry by new
    {
        indexEntry.LocationName,
        indexEntry.ItemTypeName,
    } into g
    select new
    {
        g.Key.LocationName,
        g.Key.ItemTypeName,
        Count = g.Sum(entry => entry.Count),
    };

这很好,但它只显示具有非零项目计数的 Location/ItemType 对的行。我需要让它显示所有个位置,并为每个位置显示所有项目类型,甚至是那些没有任何关联项目的项目类型。

我尝试了几种不同的方法,但到目前为止都没有成功。我的想法是将上述内容转换为 Multi-Map/Reduce 索引,然后添加另一个地图,该地图将为我提供 Locations 和 ItemTypes 的笛卡尔积,但计数为 0。然后我可以将其输入 reduce 并始终拥有每个位置/项目类型对的记录。

this.AddMap<object>(docs =>
    from itemType in docs.WhereEntityIs<ItemType>("ItemTypes")
    from location in docs.WhereEntityIs<Location>("Locations")
    select new
    {
        LocationName = location.Name,
        ItemTypeName = itemType.Name,
        Count = 0
    });

这不起作用,所以我认为 RavenDB 不喜欢这种映射。有没有办法从 RavenDB 获得交叉连接/笛卡尔积?或者,还有其他方法可以完成我想做的事情吗?

编辑:澄清一下,位置、项目类型和项目是应用程序用户创建的系统中的文档。如果系统中没有任何项目,如果用户输入三个地点“伦敦”、“巴黎”和“柏林”以及两个项目类型“桌面”和“笔记本电脑”,预期的结果是当他们查看库存摘要时,他们看到这样的表格:

| Location | Item Type | Count |
|----------|-----------|-------|
| London   | Desktop   | 0     |
| London   | Laptop    | 0     |
| Paris    | Desktop   | 0     |
| Paris    | Laptop    | 0     |
| Berlin   | Desktop   | 0     |
| Berlin   | Laptop    | 0     |

【问题讨论】:

  • 问题是你想要的是 all 与所有类型交叉的位置,对吗?问题是我们可以按位置或类型为您提供它,但是将它们组合起来会导致问题。你能先按位置,然后按类型吗?
  • @AyendeRahien 你是对的,我需要所有位置与所有类型交叉。提出请求是因为最终用户无法看到他们在特定位置“缺货”的物品。我在 RavenDB Google Group 上阅读了更多内容,并且看到一些关于笛卡尔产品由于“默认安全”心态而没有真正得到支持的讨论。就我而言,我们正在谈论数十个位置,可能还有一千种不同的类型,因此索引条目的数量并不荒谬。我不确定您所说的“先按位置再按类型”是什么意思。
  • @DavidArcher 我也面临着同样的挑战。你有没有解决过这个问题?如果是这样,您能否提供最终索引。
  • @FrederikStruck-Schøning 是的,我通过切换到 SQL Server 解决了这个问题,因为 RavenDB 无法满足要求。经验教训。
  • @DavidArcher 哎哟!我在stackoverflow.com/questions/41614013/… 面临同样的事情——离开 RavenDB 对我们来说不是一个选择,所以我想我可能不得不采用更务实的解决方案。毕竟,RavenDB 不是关系数据库,虽然它可以做一些很酷的事情,但你不能像疯了一样 JOIN :) 感谢您回来。

标签: mapreduce ravendb


【解决方案1】:

以下是您也可以对所有空位置执行此操作的方法:

AddMap<InventoryItem>(inventoryItems =>
    from inventoryItem in inventoryItems
    select new
    {
        LocationName = inventoryItem.Location.Name,
        Items = new[]{
            {
                ItemTypeName = inventoryItem.ItemType.Name,
                Count = 1}
            }
    });
)

this.AddMap<Location>(locations=>
    from location in locations
    select new
    {
        LocationName = location.Name,
        Items = new object[0]
    });


this.Reduce = results => 
    from result in results
    group result by result.LocationName into g
    select new
    {
        LocationName = g.Key,
        Items = from item in g.SelectMany(x=>x.Items)
                group item by item.ItemTypeName into gi
                select new
                {
                    ItemTypeName = gi.Key,
                    Count = gi.Sum(x=>x.Count)
                }

    };

【讨论】:

  • 我认为这不能解决我的问题。我编辑了原始问题以添加更明确的示例,但基本上即使系统中根本没有 InventoryItem 文档,我也需要得到结果。如果用户只是创建了一些 Locations 和 ItemTypes 而没有输入任何实际的 InventoryItems,那么您发布的索引上的第一张地图将不会提供任何结果,而第二张地图只会为每个位置提供一条记录。
猜你喜欢
  • 2015-10-16
  • 2014-12-06
  • 1970-01-01
  • 2015-01-15
  • 2018-11-30
  • 1970-01-01
  • 2022-01-09
  • 2017-05-27
  • 2021-05-18
相关资源
最近更新 更多