Gremlin 常用语法总结

Gremlin是 Apache TinkerPop 框架下的图遍历语言。Gremlin是一种函数式数据流语言,可以使得用户使用简洁的方式表述复杂的属性图(property graph)的遍历或查询。每个Gremlin遍历由一系列步骤(可能存在嵌套)组成,每一步都在数据流(data stream)上执行一个原子操作。

Gremlin 语言包括三个基本的操作:

  • map-step:对数据流中的对象进行转换;
  • filter-step:对数据流中的对象就行过滤;
  • sideEffect-step:对数据流进行计算统计;

Tinkerpop3 模型核心概念

  • Graph: 维护节点&边的集合,提供访问底层数据库功能,如事务功能
  • Element: 维护属性集合,和一个字符串label,表明这个element种类
  • Vertex: 继承自Element,维护了一组入度,出度的边集合
  • Edge: 继承自Element,维护一组入度,出度vertex节点集合.
  • Property: kv键值对
  • VertexProperty: 节点的属性,有一组健值对kv,还有额外的properties 集合。同时也继承自element,必须有自己的id, label.
  • Cardinality: 「single, list, set」 节点属性对应的value是单值,还是列表,或者set。

Gremlin 查询示例

下面先介绍下本文中所用到的图的 Schema(Lable):

顶点:

  • person
  • software

边:person

  • uses(person -> software)
  • develops(person -> software)
  • knows(person -> person)

下面结合上面的 Schema 给出一些常用的图操作。完整的语法见下一部分。

查询点

1
2
3
g.V().limit(100) // 查询所有点,但限制点的返回数量为100,也可以使用range(x, y)的算子,返回区间内的点数量。
g.V().hasLabel('software') // 查询点的label值为'software'的点。
g.V('11') // 查询id为‘11’的点。

查询边

1
2
3
4
g.E() // 查询所有边,不推荐使用,边数过大时,这种查询方式不合理,一般需要添加过滤条件或限制返回数量。
g.E('55-81-5') // 查询边id为‘55-81-5’的边。
g.E().hasLabel('develops') // 查询label为‘develops’的边。
g.V('46').outE('develops') // 查询点id为‘46’所有label为‘develops’的边。

查询属性

1
2
3
g.V().limit(3).valueMap() // 查询点的所有属性(可填参数,表示只查询该点, 一个点所有属性一行结果)。
g.V().limit(1).label() // 查询点的label。
g.V().limit(10).values('name') // 查询点的name属性(可不填参数,表示查询所有属性, 一个点每个属性一行结果,只有value,没有key)。

新增点

1
2
graph.addVertex(label,'person',id,'500','age','18-24') //新增点,Label为user,ID为500,age为18-24。
g.addV('person').property(id,'600').property('age','18-24') //新增点,Label为user,ID为500,age为18-24。

新增边

1
2
3
a = graph.addVertex(label,'person',id,'501','age','18-24')
b = graph.addVertex(label,'software',id,'502','title','love')
a.addEdge('develops', b, 'Year','1994') // 新增边,边的两个点ID分别为501、502。

删除点

1
g.V('600').drop() // 删除ID为600的点。

删除边

1
g.E('501-502-0').drop() //删除ID为“501-502-0”的边。

Gremlin 语法特性

基础

  1. V():查询顶点,一般作为图查询的第1步,后面可以续接的语句种类繁多。例,g.V(),g.V('v_id'),查询所有点和特定点;
  2. E():查询边,一般作为图查询的第1步,后面可以续接的语句种类繁多;
  3. id():获取顶点、边的id。例:g.V().id(),查询所有顶点的id;
  4. label():获取顶点、边的 label。例:g.V().label(),可查询所有顶点的label。
  5. key() / values():获取属性的key/value的值。
  6. properties():获取顶点、边的属性;可以和 key()、value()搭配使用,以获取属性的名称或值。例:g.V().properties('name'),查询所有顶点的 name 属性;
  7. valueMap():获取顶点、边的属性,以Map的形式体现,和properties()比较像;
  8. values():获取顶点、边的属性值。例,g.V().values() 等于 g.V().properties().value()

遍历

顶点为基准:

  1. out(label):根据指定的 Edge Label 来访问顶点的 OUT 方向邻接(可以是零个 Edge Label,代表所有类型边;也可以一个或多个 Edge Label,代表任意给定 Edge Label 的边,下同);
  2. in(label):根据指定的 Edge Label 来访问顶点的 IN 方向邻接
  3. both(label):根据指定的 Edge Label 来访问顶点的双向邻接
  4. outE(label): 根据指定的 Edge Label 来访问顶点的 OUT 方向邻接
  5. inE(label):根据指定的 Edge Label 来访问顶点的 IN 方向邻接
  6. bothE(label):根据指定的 Edge Label 来访问顶点的双向邻接

边为基准的:

  1. outV():访问边的出顶,出顶点是指边的起始顶点;
  2. inV():访问边的入顶,入顶点是指边的目标顶点,也就是箭头指向的顶点;
  3. bothV():访问边的双向顶
  4. otherV():访问边的伙伴顶,即相对于基准顶点而言的另一端的顶点;

过滤

在众多Gremlin的语句中,有一大类是filter类型,顾名思义,就是对输入的对象进行条件判断,只有满足过滤条件的对象才可以通过filter进入下一步。

has

has语句是filter类型语句的代表,能够以顶点和边的属性作为过滤条件,决定哪些对象可以通过。常用的有下面几种:

  1. has(key,value): 通过属性的名字和值来过滤顶点或边;
  2. has(label, key, value): 通过label和属性的名字和值过滤顶点和边;
  3. has(key,predicate): 通过对指定属性用条件过滤顶点和边,例:g.V().has('age', gt(20)),可得到年龄大于20的顶点;
  4. hasLabel(labels…​): 通过 label 来过滤顶点或边,满足label列表中一个即可通过;
  5. hasId(ids…​): 通过 id 来过滤顶点或者边,满足id列表中的一个即可通过;
  6. hasKey(keys…​): 通过 properties 中的若干 key 过滤顶点或边;
  7. hasValue(values…​): 通过 properties 中的若干 value 过滤顶点或边;
  8. has(key): properties 中存在 key 这个属性则通过,等价于hasKey(key);
  9. hasNot(key): 和 has(key) 相反;

例:

1
2
3
4
5
6
7
8
g.V().hasLabel('person') // lable 等于 person 的所有顶点;
g.V().has('age',inside(20,30)).values('age') // 所有年龄在20(含)~30(不含)之间的顶点;
g.V().has('age',outside(20,30)).values('age') // 所有年龄不在20(含)~30(不含)之间的顶点;
g.V().has('name',within('josh','marko')).valueMap() // name 是'josh'或'marko'的顶点的属性;
g.V().has('name',without('josh','marko')).valueMap() // name 不是'josh'或'marko'的顶点的属性;
g.V().has('name',not(within('josh','marko'))).valueMap() // 同上
g.V().properties().hasKey('age').value() // age 这个属性的所有 value
g.V().hasNot('age').values('name') // 不含 age 这个属性的所有 顶点的 name 属性

路径

在使用Gremlin对图进行分析时,关注点有时并不仅仅在最终达到顶点、边或者属性,通过什么样的路径到达最终的顶点、边和属性同样重要。此时可以借助path() 来获取经过的路径信息。

path() 返回当前遍历过的所有路径。有时需要对路径进行过滤,只选择没有环路的路径或者选择包含环路的路径,Gremlin针对这种需求提供了两种过滤路径的step:simplePath()cyclicPath()

  1. path():获取当前遍历过的所有路径;
  2. simplePath():过滤掉路径中含有环路的对象,只保留路径中不含有环路的对象;
  3. cyclicPath():过滤掉路径中不含有环路的对象,只保留路径中含有环路的对象。

例1:寻找4跳以内从 andy 到 jack 的最短路径。

1
2
3
4
g.V("andy")
.repeat(both().simplePath()).until(hasId("target_v_id")
.and().loops().is(lte(4))).hasId("jack")
.path().limit(1)

例2:“Titan” 顶点到与其有两层关系的顶点的不含环路的路径(只包含顶点)

1
2
3
4
g.V()
.hasLabel('software').has('name','Titan')
.both().both().simplePath()
.path()

迭代

  1. repeat():指定要重复执行的语句;
  2. times(): 指定要重复执行的次数,如执行3次;
    3.until():指定循环终止的条件,如一直找到某个名字的朋友为止;
  3. emit():指定循环语句的执行过程中收集数据的条件,每一步的结果只要符合条件则被收集,不指定条件时收集所有结果;
    5.loops():当前循环的次数,可用于控制最大循环次数等,如最多执行3次。

repeat()until() 的位置不同,决定了不同的循环效果:

  • repeat() + until():等同 do-while;
  • until() + repeat():等同 while-do。

repeat()emit() 的位置不同,决定了不同的循环效果:

  • repeat() + emit():先执行后收集;
  • emit() + repeat():表示先收集再执行。

注意:

emit()与times()搭配使用时,是“或”的关系而不是“与”的关系,满足两者间任意一个即可。
emit()与until()搭配使用时,是“或”的关系而不是“与”的关系,满足两者间任意一个即可。

例1:根据出边进行遍历,直到抵达叶子节点(无出边的顶点),输出其路径顶点名:

1
2
3
4
g.V(1)
.repeat(out())
.until(outE().count().is(0))
.path().by('name')

例2:查询顶点’1’的3度 OUT 可达点路径

1
2
3
4
g.V('1')
.repeat(out())
.until(loops().is(3))
.path()

转换

  1. map():可以接受一个遍历器 Step 或 Lamda 表达式,将遍历器中的元素映射(转换)成另一个类型的某个对象(一对一),以便进行下一步处理
  2. flatMap():可以接受一个遍历器 Step 或 Lamda 表达式,将遍历器中的元素映射(转换)成另一个类型的某个对象流或迭代器(一对多)。

稍微了解函数式编程的同学都比较熟悉这两个操作了,在这里不再赘述。

例:

1
2
3
4
// 创建了 titan 的作者节点的姓名
g.V('titan').in('created').map(values('name'))
// 创建了 titan 的作者节点的所有出边
g.V('titan').in('created').flatMap(outE())

排序

  1. order()
  2. order().by()

排序比较简单,直接看例子吧。

1
2
3
4
// 默认升序排列
g.V().values('name').order()
// 按照元素属性age的值升序排列,并只输出姓名
g.V().hasLabel('person').order().by('age', asc).values('name')

逻辑

  1. is():可以接受一个对象(能判断相等)或一个判断语句(如:P.gt()、P.lt()、P.inside()等),当接受的是对象时,原遍历器中的元素必须与对象相等才会保留;当接受的是判断语句时,原遍历器中的元素满足判断才会保留,其实接受一个对象相当于P.eq();
  2. and():可以接受任意数量的遍历器(traversal),原遍历器中的元素,只有在每个新遍历器中都能生成至少一个输出的情况下才会保留,相当于过滤器组合的与条件;
  3. or():可以接受任意数量的遍历器(traversal),原遍历器中的元素,只要在全部新遍历器中能生成至少一个输出的情况下就会保留,相当于过滤器组合的或条件;
  4. not():仅能接受一个遍历器(traversal),原遍历器中的元素,在新遍历器中能生成输出时会被移除,不能生成输出时则会保留,相当于过滤器的非条件

例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 筛选出顶点属性“age”等于28的属性值,与`is(P.eq(28))`等效
g.V().values('age').is(28)

// 所有包含出边“supports”和“created”的顶点的名字“name”
g.V().
and(
outE('supports'),
outE('created'))
.values('name')

// 使用 where语句 实现上面的功能
g.V().where(outE('supports')
.and()
.outE('created')
.values('name')


// 筛选出所有不是“person”的顶点的“label”
g.V().not(hasLabel('person')).label()

例2:获取所有最多只有一条“created”边并且年龄不等于28的“person”顶点

1
2
3
4
g.V().hasLabel('person')
.and(outE('created').count().is(lte(1)),
values("age").is(P.not(P.eq(28))))
.values('name')

统计

  1. sum():将 traversal 中的所有的数字求和;
  2. max():对 traversal 中的所有的数字求最大值;
  3. min():对 traversal 中的所有的数字求最小值;
  4. mean():将 traversal 中的所有的数字求均值;
  5. count():统计 traversal 中 item 总数。

例:

1
2
3
4
// 计算所有“person”的“created”出边数的均值
g.V().hasLabel('person')
.map(outE('created').count())
.mean()

分支

  1. choose() :分支选择, 常用使用场景为: choose(predicate, true-traversal, false-traversal),根据 predicate 判断,当前对象满足时,继续 true-traversal,否则继续 false-traversal;
  2. optional():类似于 by() 无实际意义,搭配 choose() 使用;

例1:if-else

1
2
3
4
g.V().hasLabel('person').
choose(values('age').is(lte(30)),
__.in(),
__.out()).values('name')

例2:if-elseif-else

1
2
3
4
5
6
7
8
9
10
11
// 查找所有的“person”类型的顶点
// 如果“age”属性等于0,输出名字
// 如果“age”属性等于28,输出年龄
// 如果“age”属性等于29,输出他开发的软件的名字
// choose(predicate).option().option()...
g.V().hasLabel('person')
.choose(values('age'))
.option(0, values('name'))
.option(28, values('age'))
.option(29, __.out('created').values('name'))
.option(none, values('name'))

补充说明

上一章简单介绍了 Gremlin 中常用的一些语法,类似诸如where()、filter、match、local、dedup()、sample()…就不一一介绍了,具体展开的话需要化很大的篇幅。

完整的语法可参考 Tinkerpop 官网文档,随用随查吧

写到最后的几点感触:Gremlin 的语法实在是太灵活了,感觉像是把各种编程语言里面的语法关键字都给打包了,学习成本还是略陡峭。

---(完)---
Yves wechat
扫一扫互相关注吧~
  • 本文作者: Yves
  • 本文标题: Gremlin 常用语法总结
  • 发布时间: 2018年11月15日 - 16:11
  • 更新时间: 2020年07月22日 - 00:07
  • 本文链接: /2018/11/15/gremlin_traversal_language/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!

扫一扫关注公众号