怪兽 - 如何合并顶点以组合它们的属性而不明确列出属性?

7
背景:我正试图使用this approach实现一个基于时间序列的版本化数据库,使用gremlin(tinkerpop v3)。

enter image description here

我希望获取给定身份节点(蓝色)的最新状态节点(红色)(由“状态”边缘链接,其中包含时间戳范围),但我想返回一个单一的聚合对象,其中包含来自身份节点的id(cid)和来自状态节点的所有属性,但我不想显式列出它们。 (8640000000000000是我表示没有“到”日期的方式-即该边缘是当前的-与所示图像略有不同)。
:> g.V().hasLabel('product').
     as('cid').
     outE('state').
     has('to', 8640000000000000).
     inV().
     as('name').
     as('price').
     select('cid', 'name','price').
     by('cid').
     by('name').
     by('price')

=>{cid=1, name="Cheese", price=2.50}
=>{cid=2, name="Ham", price=5.00}

但是,正如你所看到的,我必须列出“状态”节点的属性 - 在上面的示例中,产品的名称和价格属性。但这将适用于任何域对象,因此我不想总是列出属性。我可以在此之前运行查询以获取属性,但我认为我不应该需要运行两个查询,并且有两个往返的开销。我已经查看了“聚合”,“联合”,“折叠”等等,但似乎没有做到这一点的东西。

有什么想法吗?

===================

编辑: 基于Daniel的回答(目前还不完全符合我的要求),我将使用他的示例图表。在'modernGraph'中,人们创建->软件。如果我运行:

> g.V().hasLabel('person').valueMap()
==>[name:[marko], age:[29]]
==>[name:[vadas], age:[27]]
==>[name:[josh], age:[32]]
==>[name:[peter], age:[35]]

然后结果是一系列带有属性的实体列表。我的目标是,假设一个人只能创建一个软件(尽管希望我们稍后可以看到如何为创建的软件列表打开此功能),将创建的软件“语言”属性包含在返回的实体中,以获得:

> <run some query here>
==>[name:[marko], age:[29], lang:[java]]
==>[name:[vadas], age:[27], lang:[java]]
==>[name:[josh], age:[32], lang:[java]]
==>[name:[peter], age:[35], lang:[java]]

目前,到目前为止最好的建议如下:

> g.V().hasLabel('person').union(identity(), out("created")).valueMap().unfold().group().by {it.getKey()}.by {it.getValue()}
==>[name:[marko, lop, lop, lop, vadas, josh, ripple, peter], lang:[java, java, java, java], age:[29, 27, 32, 35]]

我希望这更清晰。如果不是,请告诉我。

3个回答

10

由于您没有提供样本图表,我将使用TinkerPop的玩具图表来展示如何操作。

假设您想合并markolop

gremlin> g = TinkerFactory.createModern().traversal()
==>graphtraversalsource[tinkergraph[vertices:6 edges:6], standard]
gremlin> g.V(1).valueMap()
==>[name:[marko],age:[29]]
gremlin> g.V(1).out("created").valueMap()
==>[name:[lop],lang:[java]]

请注意,存在两个name属性,理论上您无法预测哪个name会出现在合并结果中;但是在您的图表中似乎并不是问题。

获取两个顶点的属性:

gremlin> g.V(1).union(identity(), out("created")).valueMap()
==>[name:[marko],age:[29]]
==>[name:[lop],lang:[java]]
合并它们:
gremlin> g.V(1).union(identity(), out("created")).valueMap().
           unfold().group().by(select(keys)).by(select(values))
==>[name:[lop],lang:[java],age:[29]]

更新

感谢您提供了额外的样本输出。这使得我们更容易想出一个解决方案(虽然我认为您的输出包含错误;vadas 没有创建任何东西)。

gremlin> g.V().hasLabel("person").
           filter(outE("created")).map(
             union(valueMap(),
                   outE("created").limit(1).inV().valueMap("lang")).
             unfold().group().by {it.getKey()}.by {it.getValue()})
==>[name:[marko], lang:[java], age:[29]]
==>[name:[josh], lang:[java], age:[32]]
==>[name:[peter], lang:[java], age:[35]]

谢谢!我认为identity()调用是我在文档中错过的。不幸的是,我在我的图形和“现代”图形上都尝试了这个方法,但是两者都出现了“groovysh_evaluate类没有keys属性”的错误。有什么想法吗?所有查询都按照您上面的答案进行处理,直到最后一个查询。 - John Stephenson
你使用的TinkerPop版本是哪个?它可能比我用于测试的版本旧。如果我没记错,旧版本有.mapKeys().mapValues(),尝试使用它们。 - Daniel Kuppitz
啊,没错……我正在使用titanDb,所以我在使用Tinkerpop 3.0.1-incubating。你说的mapKeys()等是对的,但这不是一个简单的替换,因为我得到了以下结果:gremlin> g.V(1).union(identity(), out("created")).valueMap().unfold().group().by(select(mapKeys())).by(select(mapValues()))。没有静态org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.select()方法的签名适用于参数类型:(org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal) values: [[MapKeysStep]] - John Stephenson
尝试过在阅读导致“select”起源的问题后使用“by(mapKeys()).by(mapValues())”,但没有成功。我不清楚这些运算符期望或返回的类型,也不知道该去哪里查找 - 文档似乎没有到达这个层次或详细说明,否则我可能遗漏了什么。有什么想法吗?(对此给您带来的麻烦感到抱歉! :-( ) - John Stephenson
谢谢。代码可以运行,但是我没有得到原始问题中期望的结果。如果我将“g.V(1)”更改为“g.V().hasLabel('person')”,我会得到“==>[name:[marko, lop, lop, lop, vadas, josh, ripple, peter], lang:[java, java, java, java], age:[29, 27, 32, 35]]”这是每个属性键的值列表。这不是我想要的。我将更新原始问题,希望能更清楚地表达。 - John Stephenson
显示剩余4条评论

0
感谢 Daniel Kuppitzyouhans 的回答,它们给了我解决问题的基本思路。但后来我发现这个解决方案对于多行并不起作用。需要使用 local 步骤来处理多行。修改后的 Gremlin 查询如下:
g.V()
.local(
        __.union(__.valueMap(), __.outE().inV().valueMap())
        .unfold().group().by(__.select(Column.keys)).by(__.select(Column.values))
)

    

这将限制 union 和 group by 的范围为单行。

如果您可以使用自定义 DSL,可以像这个例子一样使用 Java 创建自定义 DSL。

public default GraphTraversal<S, LinkedHashMap> unpackMaps(){
        GraphTraversal<S, LinkedHashMap> it = map(x -> {
            LinkedHashMap mapSource = (LinkedHashMap) x.get();
            LinkedHashMap mapDest = new LinkedHashMap();

            mapSource.keySet().stream().forEach(key->{

                Object obj = mapSource.get(key);
                if (obj instanceof LinkedHashMap) {

                    LinkedHashMap childMap = (LinkedHashMap) obj;
                    childMap.keySet().iterator().forEachRemaining( key_child ->
                            mapDest.put(key_child,childMap.get(key_child)
                            ));


                } else
                    mapDest.put(key,obj);

            });

            return mapDest;
        });
        return it;
    }

并且可以自由地使用它

g.V().as("s")

.valueMap().as("value_map_0")
.select("s").outE("INFO1").inV().valueMap().as("value_map_1")
.select("s").outE("INFO2").inV().valueMap().as("value_map_2")
.select("s").outE("INFO3").inV().valueMap().as("value_map_3")

.select("s").local(__.outE("INFO1").count()).as("value_1")
.select("s").outE("INFO1").inV().value("name").as("value_2")


.project("val_map1","val_map2","val_map3","val1","val2")
.by(__.select("value_map_1"))
.by(__.select("value_map_2"))
.by(__.select("value_1"))
.by(__.select("value_2"))
.unpackMaps()

将结果转换为行

 map1_val1, map1_val2,.... ,map2_va1, map2_val2....,value1, value2

这可以以自然的 Gremlin 方式处理值和 valueMaps 的混合。


0
使用Gremlin Java DSL合并边缘和顶点属性:
 g.V().has('User', 'id', userDbId).outE(Edges.TWEETS)
    .union(__.identity().valueMap(), __.inV().valueMap())
    .unfold().group().by(__.select(Column.keys)).by(__.select(Column.values))
    .map(v -> converter.toTweet((Map) v.get())).toList();

不适用于多行。需要本地步骤以防止合并两行。 - Vinit Siriah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接