CosmosDB图形数据库:"upsert"查询模式

20

我是Gremlin查询语言的新手。 我需要在Cosmos DB图中插入数据(使用Gremlin.Net包),无论Vertex(或Edge)是否已存在于图中。如果数据存在,则只需更新属性。 我想使用这种模式:

g.V().hasLabel('event').has('id','1').tryNext().orElseGet {g.addV('event').has('id','1')}

但是它不受 Gremlin.Net / Cosmos DB 图形 API 的支持。有没有一种方法可以在单个查询中进行一种类似 upsert 的查询?

提前感谢。

2个回答

46

有很多方法可以做到这一点,但我认为TinkerPop社区通常采用以下方法:

g.V().has('event','id','1').
  fold().
  coalesce(unfold(),
           addV('event').property('id','1'))

基本上,它使用has()查找“事件”,并使用fold()步骤强制转换为列表。该列表将为空或其中有一个Vertex。然后使用coalesce(),尝试unfold()列表,如果它具有立即返回的Vertex,否则执行addV()

如果想要在找到元素时更新现有属性,只需在coalesce()之后添加property()步骤即可:

g.V().has('event','id','1').
  fold().
  coalesce(unfold(),
           addV('event').property('id','1')).
  property('description','This is an event')

如果您需要知道返回的顶点是“新”的还是旧的,那么可以像这样做:
g.V().has('event','id','1').
  fold().
  coalesce(unfold().
           project('vertex','exists').
             by(identity()).
             by(constant(true)),
           addV('event').property('id','1').
           project('vertex','exists').
             by(identity()).
             by(constant(false)))

关于这个主题的更多阅读可以在这个问题上找到: "为什么需要使用coalesce进行条件插入来折叠/展开?"

还要注意,可选边缘插入在这里描述: "使用gremlin添加不存在的边缘".

最后需要注意的是,虽然这个问题是关于CosmosDB的,但答案通常适用于所有TinkerPop-enabled图形。当然,图形如何优化这个Gremlin是一个单独的问题。如果一个图形具有本地upsert功能,则该功能可能会或可能不会在这个Gremlin的背后使用,因此可能有更好的方法通过图形系统本地API实现upsert(当然,选择这条路会降低代码的可移植性)。

更新:自TinkerPop 3.6.0起,fold()/coalesce()/unfold()模式已被新的mergeV()mergeE()步骤大量替换,这极大地简化了执行类似upsert操作所需的Gremlin。在3.6.0及更高版本中,您将使用以下内容替换第一个示例:

g.mergeV([(label): 'event', id: '1'])

或者更好的做法是,将名为“id”的属性键视为 T 的实际顶点标识符(我已添加了“name”属性键以帮助说明):

g.mergeV([(label): 'event', (id): '1', name: 'stephen'])

上面的代码将在地图中搜索带有 T.labelT.id 和 "name" 的顶点。如果找到,它会返回该顶点。如果没有找到,则使用这些值创建一个新的 Vertex。如果您拥有 T.id,最好直接使用如下方式:
g.mergeV([(id): '1']).
    option(onCreate, [(label): 'event', name: 'stephen'])

通过这种方式,您将搜索条件限制为仅标识符,足以唯一标识它并避免使用其他过滤器。如果未找到顶点,则会触发onCreate以使用提供的Map与搜索条件Map共同创建顶点。


如果我想查询时告诉我节点是已经存在还是刚刚创建,该怎么办? 我想到的是:g.V('1234').fold().as('existing').coalesce(unfold(), addV().property(id,'1234')).as('result').select('existing','result') 这个方法很好用,当节点被创建时返回 { 'existing': [], result: { ... } },但是如果节点已经存在,则会重复返回结果:{ 'existing': [{ /*node payload*/ }], result: { /*same node payload again*/ } } - Cristian Diaconescu
1
我认为我找到了一种更好的方法(刚学习了 store()):g.V('1235').fold().store('existing').coalesce(unfold(), addV().property(id,'1235').store('new')).cap('existing','new')。这样,要么 'existing' 要么 'new' 始终为空。如果有更好的方式来暴露布尔标志和载荷的单个属性,请告诉我。 - Cristian Diaconescu
你的方法很好,但如果可能的话最好避免副作用。我更新了我的答案,使用了无副作用的遍历,并返回一个简单的布尔值来表示是否已添加顶点。 - stephen mallette
太好了!谢谢你分享。那么如何避免副作用呢?这是因为函数式编程中对纯洁度的哲学追求,还是由于其具有可测量的副作用呢? - Cristian Diaconescu
1
从可读性的角度来看,不需要在遍历过程中参考早期点,直接从左到右阅读 Gremlin 很好。因此,我倾向于认为副作用使 Gremlin 更难以理解。此外,使用副作用构造会增加遍历的处理和内存成本,这也许是尽量避免它们的更重要的原因。 - stephen mallette

1

其实各位,这是如何做到的!

这样你就可以同时添加/更新2个顶点和一条边,并将它们关联起来!

g.V().
    has('Person', 'name', 'Ben').fold().
        coalesce(unfold().property('age', 25), 
        __.addV('Person').property('name','Ben').property('age',25)).store('start').
    V().has('Person','name','Robert').fold().
        coalesce(unfold().property('age', 41), 
        __.addV('Person').property('name','Robert').property('age',41)).
    coalesce(__.outE('link').has('id', 3).property('weight', 11), 
    __.addE('link').property('id', 3).property('weight', 10).to(select("start").unfold()))

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