【问题标题】:Is there a way to reuse aggregate steps?有没有办法重用聚合步骤?
【发布时间】:2020-09-18 14:30:22
【问题描述】:

我有一个存储不同类型实体的图形数据库,我正在构建一个 API 来从图形中获取实体。然而,它有点复杂,因为对于每种类型的实体,都有一组用于获取相关实体以及原始实体的规则。 为此,我使用了一个聚合步骤来聚合我正在获取的所有相关实体到一个集合中。

另一个要求是获取一批实体(和相关实体)。我打算通过将获取实体的has 步骤更改为使用P.within 并将聚合映射到每个找到的实体来做到这一点。 如果我继续获取单个实体,则此方法有效,但如果我想获取两个实体,那么我的结果集对于第一个实体将是正确的,但第二个实体的结果集包含第一个实体的结果以及它自己的结果。 我认为这是因为第二个将简单地从第一个添加到聚合集合中,因为聚合键是相同的。 我还没有找到任何方法来清除第一个和第二个之间的集合,也没有找到任何方法来获得动态聚合副作用键。

代码:


return graph.traversal().V()
    .hasLabel(ENTITY_LABEL)
    .has("entity_ref", P.within(entityRefs)) // entityRefs is a list of entities I am looking for
    .flatMap(
        __.aggregate("match")
          .sideEffect(
                // The logic that applies the rules lives here. It will add items to "match" collection.
          )
          .select("match")
          .fold()
    )
    .toStream()
    ...

结果应该是一个实体列表,其中外部列表​​中的第一个实体列表包含entityRefs 中第一个实体的结果,第二个实体列表包含entityRefs 中第二个实体的结果.

示例: 我想获取实体引用 AB 及其相关实体的顶点。 假设我希望结果是[[A, C], [B, D, E]],但我得到了[[A, C], [A, C, B, D, E]] 的结果(第二个结果包含第一个结果的结果)。

问题:

  1. 有没有办法在选择后清除“匹配”集合?
  2. 有没有办法拥有动态副作用键,以便我为每个 entityRef 创建一个集合?
  3. 是否有其他方法可以做到这一点?
  4. 我是否误认了问题?

编辑: 这是一个问题的缩影版本的示例。图表设置如下:

g.addV('entity').property('id',1).property('type', 'customer').as('1').
  addV('entity').property('id',2).property('type', 'email').as('2').
  addV('entity').property('id',6).property('type', 'customer').as('6').
  addV('entity').property('id',3).property('type', 'product').as('3').
  addV('entity').property('id',4).property('type', 'subLocation').as('4').
  addV('entity').property('id',7).property('type', 'location').as('7').
  addV('entity').property('id',5).property('type', 'productGroup').as('5').
  addE('AKA').from('1').to('2').
  addE('AKA').from('2').to('6').
  addE('HOSTED_AT').from('3').to('4').
  addE('LOCATED_AT').from('4').to('7').
  addE('PART_OF').from('3').to('5').iterate()

我想获取一批实体,给定它们的 ID 并获取相关实体。应该返回哪些相关实体是原始实体类型的函数。 我当前的查询是这样的(本例稍作修改):

g.V().
    hasLabel('entity').
    has('id', P.within(1,3)).
    flatMap(
        aggregate('match').
        sideEffect(
            choose(values('type')).
                option('customer',
                    both('AKA').
                        has('type', P.within('email', 'phone')).
                        sideEffect(
                            has('type', 'email').
                                aggregate('match')).
                        both('AKA').
                        has('type', 'customer').
                        aggregate('match')).
                option('product',
                    bothE('HOSTED_AT', 'PART_OF').
                        choose(label()).
                        option('PART_OF',
                            bothV().
                                has('type', P.eq('productGroup')).
                                aggregate('match')).
                        option('HOSTED_AT',
                            bothV().
                                has('type', P.eq('subLocation')).
                                aggregate('match').
                                both('LOCATED_AT').
                                has('type', P.eq('location')).
                                aggregate('match')))
        ).
        select('match').
        unfold().
        dedup().
        values('id').
        fold()
    ).
    toList()

如果我只获取一个实体,我会得到正确的结果。对于id: 1,我得到[1,2,6],对于id: 3,我得到[3,5,4,7]。但是,当我同时获取两者时,我得到:

==>[3,5,4,7]
==>[3,5,4,7,1,2,6]

第一个结果是正确的,但第二个包含两个 id 的结果。

【问题讨论】:

  • 您是否尝试过使用cap('match') 而不是select('match')
  • @KelvinLawrence 是的,不幸的是这没有帮助。如果我理解正确,cap 只是一个barrier + select 步骤?
  • 此时你不能删除副作用 - 这里有一个非常老的问题:issues.apache.org/jira/browse/TINKERPOP-1571 - 通常可以从 Gremlin 中删除所有副作用,这往往会导致更高性能和更易于阅读的代码。鉴于我读过的内容,我不确定如何以这种方式提出答案。或许您可以提供一些示例数据(例如:stackoverflow.com/questions/51388315/…)作为 Gremlin 脚本并展示更多您想要完成的任务?
  • 感谢您的澄清 - 我不完全理解您之前想要做什么。

标签: gremlin tinkerpop


【解决方案1】:

您可以利用group().by(key).by(value)(老实说,文档不太详细但看​​似强大的遍历步骤)。

这样您就可以删除给您带来麻烦的aggregate() 副作用步骤。作为将匹配某些遍历的多个顶点收集到列表中的替代方法,我使用了union()

使用您发布的图表的示例(为简洁起见,我只包括了客户选项):

g.V().
    hasLabel('entity').
    has('id', P.within(1,3)).
    <String, List<Entity>>group()
      .by("id")
      .by(choose(values("type"))
        .option('customer', union(
          identity(),
          both('AKA').has('type', 'email'),
          both('AKA').has('type', within('email', 'phone')).both('AKA').has('type', 'customer'))
          .map((traversal) -> new Entity(traversal.get())) //Or whatever business class you have 
          .fold() //This is important to collect all 3 paths in the union together
        .option('product', union()))
     .next()
    

这种遍历的明显缺点是代码有点冗长。它声明它将跨过客户的“AKA”两次。您的遍历只声明了一次。

但是,它确实将group() 步骤的by(value) 部分在不同键之间分开。这正是我们想要的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-29
    • 1970-01-01
    • 1970-01-01
    • 2020-05-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-04
    • 1970-01-01
    相关资源
    最近更新 更多