Gremlin 常用语法总结

文章目录
  1. 1. Gremlin 查询示例
    1. 1.1. 查询点
    2. 1.2. 查询边
    3. 1.3. 查询属性
    4. 1.4. 新增点
    5. 1.5. 新增边
    6. 1.6. 删除点
    7. 1.7. 删除边
  2. 2. Gremlin 语法特性
    1. 2.1. 基础
    2. 2.2. 遍历
    3. 2.3. 过滤
      1. 2.3.1. has
    4. 2.4. 路径
    5. 2.5. 迭代
    6. 2.6. 转换
    7. 2.7. 排序
    8. 2.8. 逻辑
    9. 2.9. 统计
    10. 2.10. 分支
  3. 3. 补充说明

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
5
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():指定循环终止的条件,如一直找到某个名字的朋友为止;
  4. 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 的语法实在是太灵活了,感觉像是把各种编程语言里面的语法关键字都给打包了,学习成本还是略陡峭。