图数据库Gremlin备忘

HugeGraph图模型

HugeGraph系统支持的是属性图,即图中存储的主要数据是带属性的顶点和带属性的边。顶点和边及其属性称作图数据。属性、**顶点、边、索引的定义称作元数据。**索引数据属于图数据,索引的定义属于元数据。

HugeGraph需要显示创建Schema,因此需要依次创建PropertyKey、VertexLabel、EdgeLabel,如果有需要索引还需要创建IndexLabel。在向图中增加顶点和边数据之前,顶点和边对应的类型以及属性(元数据)必须已经存在。

HugeGraph中的元数据主要用来定义和约束顶点、边、属性和索引。HugeGraph 有四种元数据:PropertyKey(属性类型)、VertexLabel(顶点类型)、EdgeLabel(边类型)和 IndexLabel(索引标签)。

以上四种元数据创建时,可以添加ifNotExist(),如…ifNotExist().create(),表示不存在时才创建,存在则返回已存在的同名元数据类型。

元数据可以通过Hubble工具来进行可视化管理

创建 TinkerPop关系图

// 创建属性名称 PropertyKey,schema = graph.schema(),graph=hugegraph(图数据库实例)
graph.schema().propertyKey("name").asText().ifNotExist().create()
graph.schema().propertyKey("age").asInt().ifNotExist().create()
graph.schema().propertyKey("addr").asText().ifNotExist().create()
graph.schema().propertyKey("lang").asText().ifNotExist().create()
graph.schema().propertyKey("tag").asText().ifNotExist().create()
graph.schema().propertyKey("weight").asFloat().ifNotExist().create()

// 创建顶点标签及其属性名称 VertexLabel
graph.schema().vertexLabel("person").properties("name", "age", "addr", "weight").useCustomizeStringId().ifNotExist().create()
graph.schema().vertexLabel("software").properties("name", "lang", "tag", "weight").primaryKeys("name").ifNotExist().create()
graph.schema().vertexLabel("language").properties("name", "lang", "weight").primaryKeys("name").ifNotExist().create()

// 创建边的标签 EdgeLabel
//graph.schema().edgeLabel("knows2").sourceLabel("person").targetLabel("person").multiTimes().sortKeys("weight").properties("weight").ifNotExist().create() // multiTimes()表示在两个顶点间某个关系可以出现多次,默认只能出现一次,设置为multiple时必须要设置sortKeys(当允许关联多次时,指定区分键属性列表)对应属性类型的值
// g.addE("knows2").property("weight", 2).from(g.V().has('name','Marko A. Rodriguez')).to(g.V().has('name','Stephen Mallette'))                          
graph.schema().edgeLabel("knows").sourceLabel("person").targetLabel("person").properties("weight").ifNotExist().create()
graph.schema().edgeLabel("created").sourceLabel("person").targetLabel("software").properties("weight").ifNotExist().create()
graph.schema().edgeLabel("contains").sourceLabel("software").targetLabel("software").properties("weight").ifNotExist().create()
graph.schema().edgeLabel("define").sourceLabel("software").targetLabel("language").properties("weight").ifNotExist().create()
graph.schema().edgeLabel("implements").sourceLabel("software").targetLabel("software").properties("weight").ifNotExist().create()
graph.schema().edgeLabel("supports").sourceLabel("software").targetLabel("language").properties("weight").ifNotExist().create()


// 创建节点与关系                                                                                                 
                                                                                              
// TinkerPop 
okram = graph.addVertex(T.label, "person", T.id, "okram", "name", "Marko A. Rodriguez", "age", 29, "addr", "Santa Fe, New Mexico", "weight", 1)
spmallette = graph.addVertex(T.label, "person", T.id, "spmallette", "name", "Stephen Mallette", "age", 0, "addr", "", "weight", 1)

tinkerpop = graph.addVertex(T.label, "software", "name", "TinkerPop", "lang", "java", "tag", "Graph computing framework", "weight", 1)
tinkergraph = graph.addVertex(T.label, "software", "name", "TinkerGraph", "lang", "java", "tag", "In-memory property graph", "weight", 1)
gremlin = graph.addVertex(T.label, "language", "name", "Gremlin", "lang", "groovy/python/javascript", "weight", 1)

okram.addEdge("created", tinkerpop, "weight", 1)
spmallette.addEdge("created", tinkerpop, "weight", 1)

okram.addEdge("knows", spmallette, "weight", 1)

tinkerpop.addEdge("define", gremlin, "weight", 1)
tinkerpop.addEdge("contains", tinkergraph, "weight", 1)
tinkergraph.addEdge("supports", gremlin, "weight", 1)

// Titan
dalaro = graph.addVertex(T.label, "person", T.id, "dalaro", "name", "Dan LaRocque ", "age", 0, "addr", "", "weight", 1)
mbroecheler = graph.addVertex(T.label, "person", T.id, "mbroecheler", "name", "Matthias Broecheler", "age", 29, "addr", "San Francisco", "weight", 1)

titan = graph.addVertex(T.label, "software", "name", "Titan", "lang", "java", "tag", "Graph Database", "weight", 1)

dalaro.addEdge("created", titan, "weight", 1)
mbroecheler.addEdge("created", titan, "weight", 1)
okram.addEdge("created", titan, "weight", 1)

dalaro.addEdge("knows", mbroecheler, "weight", 1)

titan.addEdge("implements", tinkerpop, "weight", 1)
titan.addEdge("supports", gremlin, "weight", 1)

// HugeGraph
javeme = graph.addVertex(T.label, "person", T.id, "javeme", "name", "Jermy Li", "age", 29, "addr", "Beijing", "weight", 1)
zhoney = graph.addVertex(T.label, "person", T.id, "zhoney", "name", "Zhoney Zhang", "age", 29, "addr", "Beijing", "weight", 1)
linary = graph.addVertex(T.label, "person", T.id, "linary", "name", "Linary Li", "age", 28, "addr", "Wuhan. Hubei", "weight", 1)

hugegraph = graph.addVertex(T.label, "software", "name", "HugeGraph", "lang", "java", "tag", "Graph Database", "weight", 1)

javeme.addEdge("created", hugegraph, "weight", 1)
zhoney.addEdge("created", hugegraph, "weight", 1)
linary.addEdge("created", hugegraph, "weight", 1)

javeme.addEdge("knows", zhoney, "weight", 1)
javeme.addEdge("knows", linary, "weight", 1)

hugegraph.addEdge("implements", tinkerpop, "weight", 1)
hugegraph.addEdge("supports", gremlin, "weight", 1)

一.基础 Steps

顶点与边均由id来唯一标识,Gremlin里顶点与边必须包括id,一般图数据库的顶点id或边id均由系统自动生成

1. V():查询顶点

查询顶点,一般作为图查询的第1步,后面可以续接的语句种类繁多

// 查询图中所有的顶点
// 注意:g 代表的是整个图
// 一切查询都是以图开始
g.V() // 等价于 hugegraph.traversal().V(),其中 g=graph.traversal(),graph=hugegraph(图数据库实例)

// 根据id查询顶点,多个以逗号隔开
g.V('okram','3:Gremlin')

2. E():查询边

查询边,一般作为图查询的第1步,后面可以续接的语句种类繁多

// 查询图中所有的边
g.E()

// 根据id查询边
g.E('S2:TinkerPop>4>>S3:Gremlin')

3. id():获取顶点、边的id

// 查询所有顶点的id,注意:Gremlin Step是作用在上一步产生的结果集上,如果上一步的结果是多个元素,那么这里id()将返回多个元素的id。
g.V().id()

// 查询指定顶点的id
g.V('okram','3:Gremlin').id()
g.V(["okram","3:Gremlin"]).id()

// 查询指定边的id
g.E('S2:TinkerPop>4>>S3:Gremlin').id()

4. label():获取顶点、边的label

// 查询所有顶点的label
g.V().label()

// 查询指定顶点的label
g.V('okram','3:Gremlin').label()

// 查询所有边的label
g.E().label()

5. properties():获取顶点、边的属性,返回的结果是字典形式:每个对象每个属性一条

可分别获取属性名称、属性值

// 查询所有顶点的属性
g.V().properties()

// 查询所有顶点的“lang”属性,如果无“lang”属性的顶点将跳过,有'lang'的顶点只留下'lang'属性
g.V().properties('lang')

// 查询所有边的属性
g.E().properties()

// properties()还可以和 key()、value()搭配使用,以获取属性的名称或值
// 查询指定顶点的属性名称
g.V('okram','3:Gremlin').properties().key()
g.V('okram','3:Gremlin').properties('lang').key()
// 查询指定顶点的属性值
g.V('okram','3:Gremlin').properties().value()
g.V('okram','3:Gremlin').properties('lang').value()

6. values():获取顶点、边的属性值

// 查询所有顶点的属性值,效果等同于:g.V().properties().value()
g.V().values()
  
// 查询所有顶点的“lang”属性值,效果等同于:g.V().properties('lang').value()
g.V().values('lang')

7. valueMap():获取顶点、边的属性,返回字典形式的结果:所有属性在一个字典里面。

valueMap()properties()不同的地方是:它们返回的结构不一样,后者将所有的属性扁平化到一个大列表里面,一个元素代表一个属性(一条);前者保持一个顶点或一条边的属性作为一组,每一组由若干属性的键值对组成。

// 查询指定顶点的属性
g.V('okram','3:Gremlin').valueMap()

// 查询所有顶点字典形式的name属性,没有这个属性的对象返回空字典
g.V().valueMap('name')

二.边的遍历操作

边遍历是指通过顶点来访问与其有关联边的邻接顶点或邻接边,边遍历是图数据库与图计算的核心

以顶点为基准的Steps:

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

以边为基准的Steps:

  • outV(): 访问边的出顶点(注意:这里是以边为基准,上述Step均以顶点为基准),出顶点是指边的起始顶点
  • inV(): 访问边的入顶点,入顶点是指边的目标顶点,也就是箭头指向的顶点
  • bothV(): 访问边的双向顶点
  • otherV(): 访问边的伙伴顶点,即相对于基准顶点而言的另一端的顶点

1.1 out():访问顶点的OUT方向邻接点

即由此顶点通过边连接的顶点,可以通过label限制关联边类型,图像上看只展示邻接点,如果想展示出路径,可在结果后面使用path()画出路径

// 先查询图中所有的顶点,然后访问顶点的OUT方向邻接点(遍历每个顶点的OUT方向邻接点,所以结果可能会有重复顶点)。注意:out()的基准必须是顶点
g.V().out()
// 查询指定顶点的OUT方向邻接点
g.V('okram').out() // 'okram'这个顶点有三个邻接点,其中两个通过'created'边连接,另外一个通过'knows'边连接。

// 查询指定边类型的OUT方向邻接点,可指定多种边类型
g.V('okram').out('created') // 只返回通过'created'边连接的两个邻接点
g.V('okram').out('created','knows') 返回通过'created'边与'knows'边连接的OUT方向邻接点

1.2 in():访问顶点的IN方向邻接点

即此顶点来自哪些顶点,可以通过label限制关联边类型,图像上看只展示邻接点

// 指定顶点的in方向邻接点
g.V('3:Gremlin').in() // '3:Gremlin'这个顶点来自三个顶点,其中3个顶点通过'supports'边连接'3:Gremlin',另外一个通过'define'边连接它。
g.V('3:Gremlin').in('supports') // 只返回通过'supports'边连接'3:Gremlin'这个顶点的3个邻接点

1.3 both():访问顶点的双向邻接点

可以通过label限制关联边类型,图像上看只展示邻接点

// 访问某个顶点的双向邻接点
g.V('2:TinkerPop').both()

// 访问某个顶点的双向邻接点且限制了关联边的类型
g.V('2:TinkerPop').both('implements', 'define')

1.4 outE(): 访问顶点的OUT方向邻接边

图像上看展示了顶点、该顶点的邻接边和邻接点

// 访问某个顶点的OUT方向邻接边
g.V('2:TinkerPop').outE() // '2:TinkerPop'这个顶点out方向有两条边,其中一条为'define',另外一条为'contains';
g.V('2:TinkerPop').outE('define')

1.5 inE(): 访问顶点的IN方向邻接边

图像上看展示了顶点、连接该顶点的邻接边和邻接点

// 访问某个顶点的IN方向邻接边
g.V('2:TinkerPop').inE() // '2:TinkerPop'这个顶点in方向有4条边,其中2条为'created',另外2条为'implements';
g.V('2:TinkerPop').inE('created')

1.6 bothE(): 访问顶点的双向邻接边

g.V('2:TinkerPop').bothE()
g.V('2:TinkerPop').bothE('define','created')

2.1 outV(): 访问边的出顶点

注意:这里是以边为基准,出顶点是指边的起始顶点

// 访问某个顶点的IN邻接边,然后获取边的出顶点,等价于 g.V('2:TinkerPop').in()
g.V('2:TinkerPop').inE().outV()

2.2 inV(): 访问边的入顶点

注意:这里是以边为基准,入顶点是指边的指向顶点

// 访问某个顶点的OUT邻接边,然后获取边的入顶点,等价于 g.V('2:TinkerPop').out()
g.V('2:TinkerPop').outE().inV()

2.3 bothV(): 访问边的双向顶点

// 访问某个顶点的OUT邻接边,然后获取边的双向顶点。注意:bothV()会把源顶点也一起返回,因此只要源顶点有多少条出边,结果集中就会出现多少次源顶点
g.V('2:TinkerPop').outE().bothV()

2.4 otherV() : 访问边的伙伴顶点

即相对于基准顶点而言的另一端的顶点

// 访问某个顶点的OUT邻接边,然后获取边的伙伴顶点,一般情况下,outE().otherV()等价于out(),inE().otherV()等价于in(),bothE().otherV()等价于both()
g.V('2:TinkerPop').outE().otherV()

练习

// 查询支持Gremlin语言的软件的作者
g.V('3:Gremlin').in('supports').in('created')

// 查询某个作者的共同作者
// 通过id找到“javeme”作者顶点
// 通过out()访问其创建的软件
// 通过in()访问软件的所有作者
g.V('javeme').out('created').in('created')

三.has条件过滤

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

has语句是filter类型语句的代表,能够以顶点和边的属性作为过滤条件,决定哪些对象可以通过。has语句包括很多变种:

  • hasLabel(labels…): object的label与labels列表中任何一个匹配就可以通过,筛选顶点或者边
  • hasId(ids…): object的id满足ids列表中的任何一个就可以通过,筛选顶点或者边
  • hasKey(keys…): object的属性键包含所有的keys列表成员才能通过,配合perperties()使用,作用于顶点属性
  • hasValue(values…): object的属性值包含所有的values列表成员才能通过,配合perperties()使用,作用于顶点属性
  • has(key): 包含键为key的属性的object通过,作用于顶点或者边
  • hasNot(key): 不包含键为key的属性的object通过,作用于顶点或者边
  • has(key, value): 包含属性“key=value”的object通过,作用于顶点或者边
  • has(label, key, value): 包含属性“key=value”且label值匹配的object通过,作用于顶点或者边
  • has(key, predicate): 包含键为key且对应的value满足predicate的object通过,作用于顶点或者边

1.1 has(key)与hasNot(key):通过属性的名字来过滤顶点或边

// 查询包含属性“age”的顶点
g.V().has('age')

// 查询没有属性“age”的顶点
g.V().hasNot('age')

1.2 has(key, value):通过属性的名字和值来过滤顶点或边

在HugeGraph中,按property的值查询之前,应该对property建立索引,否则将无法查到结果并引发异常。(有两个例外,Vertex的PrimaryKeys和Edge的SortKeys)

// 对‘person’的‘addr’属性建立secondary索引,可以对Text类型property按值进行查询
graph.schema().indexLabel('personByCity').onV('person').by('addr').secondary().ifNotExist().create()
// 对‘person’的‘age’属性建立range索引,可以对Number类型property进行范围查询
graph.schema().indexLabel('personByAge').onV('person').by('age').range().ifNotExist().create()
// 对‘person’的‘name’属性建立secondary索引,可以对Text类型property按值进行查询
graph.schema().indexLabel('personByName').onV('person').by('name').secondary().ifNotExist().create()

// 查询包含属性“age”的顶点
g.V().has('addr')

// 查询没有属性“age”的顶点
g.V().hasNot('age')

// 查询“addr”属性值为“Beijing”的顶点
g.V().has('addr', 'Beijing')

2. has(key, predicate):通过对指定属性用条件过滤顶点和边

// 查询“addr”属性值为“Beijing”的顶点
g.V().has('age', gt(20))

3. has(label, key, value):通过label和属性的名字和值过滤顶点和边

// 查询label为“person”且“addr”属性值为“Beijing”的顶点
g.V().has('person', 'addr', 'Beijing')

4. hasLabel(labels...):通过label来过滤顶点或边

满足label列表中一个即可通过

// 查询label为"person"的顶点
g.V().hasLabel('person')
// 查询label为"person"或者"software"的顶点
g.V().hasLabel('person', 'software')

5. hasId(ids…):通过id来过滤顶点或者边

满足id列表中的一个即可通过

// 查询id为“zhoney”或者“3:HugeGraph”的顶点,等价于 g.V('zhoney', '2:HugeGraph')
g.V().hasId('zhoney', '2:HugeGraph')

6. hasKey(keys…): 作用于顶点的属性(基于的数据是:每个对象每个属性一条),按属性键过滤并返回属性

注意:直接将hasKey()作用于顶点,仅后端是Cassandra时支持这种写法:g.V().hasKey(‘lang’)

// 查询所有顶点的属性键中包含“lang”的顶点并返回其属性,等价于g.V().properties('lang')
g.V().properties().hasKey('lang')
g.V().properties().hasKey('lang','age')

7. hasValue(values…): 作用于顶点的属性(基于的数据是:每个对象每个属性一条),按属性值过滤并返回属性

注意:直接将hasValue()作用于顶点,仅后端是Cassandra时支持这种写法:g.V().hasValue(‘Beijing’)

// 查询所有顶点的属性值中包含“Beijing”的顶点并返回其属性
g.V().properties().hasValue('Beijing')
g.V().properties().hasValue('Beijing','Wuhan. Hubei')

四.图查询返回结果数限制

Gremlin能统计查询结果集中元素的个数,且允许从结果集中做范围截取。假设某个查询操作(如:g.V())的结果集包含8个元素,我们可以从这8个元素中截取指定部分。主要包括:

  • count(): 统计查询结果集中元素的个数;
  • range(m, n): 指定下界和上界的截取,左闭右开。比如range(2, 5)能获取第2个到第4个元素(0作为首个元素,上界为-1时表示剩余全部);
  • limit(n): 下界固定为0,指定上界的截取,等效于range(0, n),语义是“获取前n个元素”。比如limit(3)能获取前3个元素;
  • tail(n): 上界固定为-1,指定下界的截取,等效于range(count - n, -1),语义是“获取后n个元素”。比如tail(2)能获取最后的2个元素;
  • skip(n): 上界固定为-1,指定下界的截取,等效于range(n, -1),语义是“跳过前n个元素,获取剩余的元素”。比如skip(6)能跳过前6个元素,获取最后2个元素。

1. count():查询当前traverser中的元素的个数

元素可以是顶点、边、属性、路径等

// 查询图中所有顶点的个数
g.V().count()

// 查询图中类型为“人person”的顶点数
g.V().hasLabel('person').count()

// 查询图中所有的 “人创建created” 的边数
g.V().hasLabel('person').outE('created').count()

// 查询图中所有顶点的属性数
g.V().properties().count()

2. range():限定查询返回的元素的范围

上下界表示元素的偏移量,左闭右开。下界以“0”作为第一个元素,上界为“-1”时表示取到最后的元素

// 查询类型为“人person”的顶点中的第5个到最后一个
g.V().hasLabel('person').range(5, -1)

3. limit():查询前“n”个元素,相当于`range(0, n)

// 查询前三条边
g.E().limit(3)

4. tail():与limit()相反,它查询的是后“n”个元素,相当于`range(count - n, -1)

// 查询后两个顶点
g.V().tail(2)

5. skip():跳过前“n”个元素,获取剩余的全部元素

// 跳过前5个,skip(5)等价于range(5, -1)
g.V().hasLabel('person').skip(5)

五.查询路径path

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

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

1. path():获取当前遍历过的所有路径

// “HugeGraph”顶点到与其有直接关联的顶点的路径(仅包含顶点)
g.V().hasLabel('software').has('name','HugeGraph').both().path()

// 如果想要同时获得经过的边的信息,可以用bothE().otherV()替换both()
// “HugeGraph”顶点到与其有直接关联的顶点的路径(包含顶点和边)
g.V().hasLabel('software').has('name','HugeGraph')
 .bothE().otherV().path()
 
// 输出路径的时候,通过by(property)语句可以指定对象的某个属性代替对象,且连续的多个by()是循环应用到路径中的对象,例如路径中有3个对象[A, B, C],by(X).by(Y)语句指定两个属性[X Y],代表用“用A的X属性代表A,用B的Y属性代表B,用C的X属性代表C”
// “HugeGraph”顶点到与其有直接关联的顶点的路径(包含顶点和边)
// 用“name”属性代表person和software顶点,用“weight”属性代表边
g.V().hasLabel('software').has('name','HugeGraph')
 .bothE().otherV().path().by('name').by('weight')

路径分为两种:有环路径和无环路径。

  • 有环路径是指 一条路径中 至少有一个对象出现的次数大于等于两次。
  • 无环路径是指路径中所有的对象只出现一次。
// “HugeGraph”顶点到与其有两层关系的顶点的所有路径(只包含顶点)
g.V().hasLabel('software').has('name','HugeGraph')
 .both().both().path()

图中红框内的是含有环路的路径,其他的是不含有环路的路径



2. simplePath():过滤掉路径中含有环路的对象

只保留路径中不含有环路的对象

// “HugeGraph”顶点到与其有两层关系的顶点的不含环路的路径(只包含顶点)
g.V().hasLabel('software').has('name','HugeGraph')
 .both().both().simplePath().path()

3. cyclicPath():过滤掉路径中不含有环路的对象

只保留路径中含有环路的对象

// “HugeGraph”顶点到与其有两层关系的顶点的包含环路的路径(只包含顶点)
g.V().hasLabel('software').has('name','HugeGraph')
 .both().both().cyclicPath().path()

六.循环操作

循环操作是指多次执行某一部分语句,用于语句需要重复运行的场景,比如“查找朋友的朋友的朋友”,可以直接使用循环操作来完成即“查找3层朋友”,下面对具体的循环相关的Step进行说明:

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

1. repeat() + times():按照指定的次数重复执行语句

// 访问某个顶点的OUT邻接点(1次)
g.V('okram').repeat(out()).times(1)

// 访问某个顶点的2度双向邻接点,先访问第1个顶点的所有邻接点(第1层,再访问第1层结果顶点的邻接点(第2层)
g.V('okram').repeat(both()).times(2)

2. repeat() + until():根据条件来重复执行语句

until()times()是互斥的,两个语句无法同时存在于同一个循环中。

// 查询顶点'okram'到顶点'Gremlin'之间的路径
// 循环的终止条件是遇到名称是'Gremlin'的顶点
g.V('okram')
 .repeat(out())
 .until(has('name', 'Gremlin'))
 .path()
 
// 查询顶点'okram'到顶点'Gremlin'之间的路径
// 且之间只相差2跳的距离
// 其中的and()是指两个条件都满足
g.V('okram')
 .repeat(out())
 .until(has('name', 'Gremlin')
        .and().loops().is(2))
 .path()
 
// until()放在repeat()之前或之后的顺序是会影响逻辑的,放前面表示先判断再执行,放后面表示先执行后判断。请对比如下两个语句的执行结果:
g.V('okram').repeat(out()).until(hasLabel('person')).path()
g.V('okram').until(hasLabel('person')).repeat(out()).path()

3. epeat() + emit():收集执行过程中的数据

// 查询顶点'okram'的所有OUT可达点的路径
g.V('okram')
 .repeat(out())
 .emit()
 .path()
 
emit()放在repeat()之前或之后的顺序是会影响结果的,放前面表示先收集再执行,放后面表示先执行后收集。请对比如下两个语句的执行结果:
g.V('okram').repeat(out()).emit(hasLabel('person')).path()
g.V('okram').emit(hasLabel('person')).repeat(out()).path()

// 查询顶点'okram'到顶点'Gremlin'之间的路径
// 此外还收集过程中的'person'类型的顶点
g.V('okram')
 .repeat(out())
 .until(has('name', 'Gremlin'))
 .emit(hasLabel('person'))
 .path()
 
 // 查询顶点'okram'的2度OUT可达点的路径
// 此外还收集'person'类型的顶点
g.V('okram')
 .repeat(out()).times(2)
 .emit(hasLabel('person'))
 .path()

4.repeat() + loops():根据最大次数限制来重复执行语句

// 查询顶点'okram'的3度OUT可达点路径
g.V('okram')
 .repeat(out())
 .until(loops().is(3))
 .path()
 
// 查询顶点'okram'到顶点'Gremlin'之间的路径
// 且之间只相差2跳的距离
// 其中的and()是指两个条件都满足
g.V('okram')
 .repeat(out())
 .until(has('name', 'Gremlin')
        .and().loops().is(2))
 .path()
 
// 查找子树:查找从一个节点出发,到
// 叶子节点结束的所有路径
// 这些路径的集合为一颗子树(子图)
g.V('okram')
 .repeat(out())
 .until(outE().count().is(0))
 .path()
 
// 查找两点间最短路径
// 已知两个顶点'okram'和'javeme',
// 通过任意关系来找到这两点之间的路径
// 且限制了最大深度为3
// 若存在那么第一条结果即是最短路径
g.V('okram')
 .repeat(bothE().otherV().simplePath())
 .until(hasId('javeme').and().loops().is(lte(3)))
 .hasId('javeme')
 .path()

七.查询结果排序

Gremlin允许对查询的结果进行排序输出,可以指定按某个属性的升序、降序或是随机序的方式输出。排序方式可以通过单独的order()或者order().by(...)指定,而by() step又有一些变种

1. 单独使用order() Step

一般用于遍历器中的元素是属性时,order()会将结果以升序输出,order()单独使用时,必须保证遍历器(traverser)中的元素是可排序的,否则会抛出异常

// 以默认排序(升序)输出所有顶点的"name"属性值
g.V().values('name').order()

2. 联合使用order().by(...) Step,传入排序方式,一般用于遍历器中的元素是属性时

使用 order().by(…) step 但是 by() 传递的仅是一个排序方式的参数时,也必须保证遍历器(traverser)中的元素是可排序的。

  • order().by(incr): 将结果以升序输出,这也是默认的排序方式;
  • order().by(decr): 将结果以降序输出;
  • order().by(shuffle): 将结果以随机序输出,每次执行结果顺序都可能不一样。
// 以降序输出所有顶点的"name"属性值
g.V().values('name').order().by(decr)

3. 联合使用order().by(...) Step,传入属性和排序方式,用于遍历器中的元素是顶点或边时

by()step不是一个真正的step,而是一个调整步“step modulator”,与此类似的还有as()option()step。通过by()step可以为某些step添加traversal、function、comparator等,通常的使用方式是step().by()…by(),某些step只能添加一个by(),而有一些可以添加任意数量的by()step。

  • order().by(key): 将结果按照元素属性key的值升序排列,与order().by(key, incr)等效;
  • order().by(key, incr): 将结果按照元素属性key的值升序排列;
  • order().by(key, decr): 将结果按照元素属性key的值降序排列;
  • order().by(key, shuffle): 将结果按照元素属性key的值随机序排列,每次执行结果顺序都可能不一样。
// 将"person"类型的顶点按照"age"升序(默认)排列输出
g.V().hasLabel('person').order().by('age')

// 将"person"类型的顶点按照"age"升序(默认)排列,并获取"age"属性
g.V().hasLabel('person').order().by('age').values('age')

// 将"person"类型的顶点按照"age"降序排列输出,并获取"age"属性
g.V().hasLabel('person').order().by('age', decr).values('age')

八.数据分组与去重

Gremlin支持对数据进行分组和去重。

数据分组是指:从某个维度上对拥有相同点的数据进行分组,比如根据年龄分组、根据出生省份分组等。

数据去重是指:去除结果集中相同的元素,或者去除在某个维度上具有相同点的数据,比如根据年龄选出一些代表,每个年龄最多只能有一个人。

下面讲解实现上述功能的具体Step:

  • group(): 对结果集进行分组,可通过by(property)来指定根据什么维度进行分组,可称维度为分组键;如果不指定维度则以元素id作为分组键,相当于重复的元素被分为一组。每一组由分组键+组内元素列表构成。如果有需要也可对每一组的元素列表进行reduce操作,依然使用by()语句,如by(count())对组内元素计数。
  • groupCount(): 对结果集进行分组,若不指定分组维度则默认统计每一组元素个数。每一组由分组键+组内元素数量构成。
  • dedup(): 去除结果集中相同的元素,可通过by(property)来指定根据什么维度进行去重。
  • by(): 语义上一般指“根据什么维度”,与上述语句配合使用,如group().by()dedup().by()等。也可与其它语句配合,如前面讲到的排序order().by()及路径path().by()等。

1. group():对结果集进行分组

group()仅只是分组,并未汇总统计,除非使用by来汇总统计

// 不指定任何维度进行分组,如果不指定维度则以元素id作为分组键
g.V().hasLabel('person').group()

// 不指定任何维度进行分组,但数据集中有重复的元素,重复的元素将会被分为一组
g.V().both().hasLabel('person').group()

// 根据年龄进行分组
g.V().hasLabel('person').group().by('age')

// 根据年龄进行分组(被分组的所有元素都要有分组字段),并统计各个年龄的人数
g.V().hasLabel('person')
 .group().by('age').by(count())
 
// 根据顶点类别(标签)进行分组,并统计各个类别的数量
g.V().group().by(label).by(count())

2. groupCount():对结果集进行分组计数

如果没有指定分组维度,则groupCount()先按元素id分组,然后统计每个元素数量

如果指定了分组维度,则groupCount()先按指定维度分组,然后统计每个维度值的数量

// 不指定任何维度进行分组计数
g.V().hasLabel('person').groupCount()

// 不指定任何维度进行分组计数,但数据集中有重复的元素,重复的元素将会被分为一组
g.V().both().hasLabel('person').groupCount()

// 根据年龄进行分组计数,等价于:g.V().hasLabel('person').group().by('age').by(count())或g.V().hasLabel('person').values('age').groupCount()
g.V().hasLabel('person')
 .groupCount().by('age')
 
// 拥有相同数量边的顶点作为一组,并获取每一组的顶点数量,结果相当于:拥有m条边的顶点有n个
g.V().groupCount().by(bothE().count())

3. dedup():去除结果集中重复的元素

可通过by(property)来指定根据什么维度进行去重

// 对一组含有重复顶点的数据进行去重
g.V().both().hasLabel('person').dedup()

// 查看所有人当中有哪几种年龄,人之间的年龄是可能有重复的,通过dedup去除掉重复的年龄
g.V().hasLabel('person')
 .values('age').dedup()
 
 // 每个年龄留一个代表
g.V().hasLabel('person').dedup().by('age')

九.条件和过滤

在对图进行遍历分析时,经常需要对满足一定条件的对象进行过滤。where()就是用来过滤遍历过程中当前阶段的对象。另一方面,predicate就是过滤时使用的判断条件,包括关系运算和区间判断等,只有满足判断条件的对象才能通过进入下一轮或者作为结果。

在众多的Gremlin steps中,有一大类是filter step,通过判断是否满足predicate来决定对象能否通过filter stepfilter()语句是filter step的基础,较为抽象,而更加具体的where()语句就是一个典型的filter step

where()常与select()或者match()配合使用,也可以单独使用。

以下是predicate的说明:

Predicate Description
eq(object) 传入的对象等于目标object?
neq(object) 传入的对象不等于目标object?
lt(number) 传入的数字小于目标number?
lte(number) 传入的数字小于或等于目标number?
gt(number) 传入的数字大于目标number?
gte(number) 传入的数字大于或等于目标number?
inside(low,high) 传入的数字大于low且小于high?
outside(low,high) 传入的数字小于low或者大于high?
between(low,high) 传入的数字大于等于low且小于high?
within(objects…) 传入的对象等于目标对象列表objects中的任意一个?
without(objects…) 传入的对象不等于目标对象列表objects中的任何一个?

1.可以通过test()来测试真假

// 判断3是否等于2
eq(2).test(3)

// 判断d是否在abc中
within('a','b','c').test('d')

// not()作用于neq(),等价于eq()
not(eq(2)).test(3)

// and()连接的predicate,是一个新的predicate
within(1,2,3).and(not(eq(2))).test(3)

// or()连接的predicate,是一个新的predicate
inside(1,4).or(eq(5)).test(3)

2. where()单独使用

2.1 where()有三种使用方式:

  • where(P)
  • where(String, P)
  • where(Traversal)
// 查看“zhoney”的合作伙伴:where(P)方式。这里的as('a')相当于给对象一个别名,不对步骤产生其他影响。这句代码可以这样理解:先查找出'zhoney'这个顶点,然后给'zhoney'这个顶点命名别名为'a',然后找到其出边为'created'对应的所有顶点(即他created的软件),然后再找到这些顶点入边为'created'对应的所有顶点(此处会包含'zhoney'这个顶点),然后使用where过滤掉结果集中名称为'a'(即'zhoney'的别名)的顶点。
g.V('zhoney').as('a')
 .out('created').in('created')
 .where(neq('a'))
 
// 查看“zhoney”的合作伙伴,where(String, P)方式
g.V('zhoney').as('a')
 .out('created').in('created').as('b')
 .where('a',neq('b'))
 
// 查找出“spmallette”的合作伙伴中,开发过不止一个软件的人,使用where(Traversal)方式。这句代码可以这样理解:先查找出'spmallette'这个顶点,然后找到其出边为'created'对应的所有顶点(即他created的软件),然后再找到这些顶点入边为'created'对应的所有顶点(即created这些软件的所有人),基于得到的这些顶点:对于每个顶点,计算出边为'created'对应的所有顶点的个数,筛选出个数大于1的顶点
g.V('spmallette').out('created').in('created')
 .where(out('created').count().is(gt(1)))
 .values('name')

2.2 where()可以与by()语句配合使用,表示用by(property)指定的属性进行predicate判断

// 查询认识自己的人中年龄大于自己的年龄的。这句代码可以这样理解:对于所有顶点,别名为'a',找出每个顶点出边为'knows'的顶点并命名为'b',然后比较每个'a'与其'knows'的所有'b'的年龄。
g.V().as('a')
 .out('knows').as('b')
 .where('a', gt('b')).by('age')

2.3 where()as()+select()配合使用:as()可以为某一阶段的对象添加标签,select()则可以通过标签获取对象。因此as()+select()可以在某个step处得到历史信息

// 查看“zhoney”的合作伙伴,并将“zhoney”及其合作伙伴的名字以map输出
// select().where()方式
g.V('zhoney').as('a')
 .out('created').in('created').as('b')
 .select('a','b').by('name')
 .where('a',neq('b'))

2.4 where()match()配合使用,match表示模式匹配

// 查看“zhoney”的合作伙伴,并将“zhoney”及其合作伙伴的名字以map输出
// match().where()方式
g.V('zhoney').match(__.as('a').out('created').as('b'),
                    __.as('b').in('created').as('c')
                   )
                 	 .where('a', neq('c'))
             			 .select('a','c').by('name')

3.filter()有三种用法:

  • lambda方式,filter{it.get()…}
  • Traversal方式,filter(Traversal)
  • 特定filter step方式
// 查找图中的“person”顶点
// lambda方式
g.V().filter {it.get().label() == 'person'}

// 查找图中的“person”顶点
// Traversal方式
g.V().filter(label().is('person'))


// 查找图中的“person”顶点
// 特定filter step方式
g.V().hasLabel('person')

十.逻辑运算

Gremlin支持在遍历器上加上逻辑运算进行过滤,只有满足该逻辑条件的元素才会进入下一个遍历器中。

下面讲解实现上述功能的具体Step:

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

这四种逻辑运算Step除了像一般的Step写法以外,and()or()还可以放在where()中以中缀符的形式出现。

1. is()

// 筛选出顶点属性“age”等于28的属性值,与`is(P.eq(28))`等效
g.V().values('age').is(28)

// 筛选出顶点属性“age”大于等于28的属性值
g.V().values('age').is(gte(28))

// 筛选出顶点属性“age”属于区间(27,29)的属性值
g.V().values('age').is(inside(27, 29))

// 筛选出顶点属性“age”属于区间(27,29)的属性值,P.inside(a, b)是左开右开区间(a,b)
g.V().values('age').is(inside(27, 29))

// 筛选出由两个或两个以上的人参与创建(“created”)的顶点
// 注意:这里筛选的是顶点
g.V().where(__.in('created').count().is(gt(2))).values('name')

// 筛选出有创建者(“created”)的年龄(“age”)在20~29之间的顶点
g.V().where(__.in('created').values('age').is(between(20, 29))).values('name')

2. and(),逻辑与

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

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

// 包含边“created”并且属性“age”为28的顶点的名字“name”
g.V().and(outE('created'), values('age').is(28)).values('name')

// 同上,放在where()里面的用法。包含边“created”并且属性“age”为28的顶点的名字“name”
g.V().where(outE('created')
            .and()
            .values('age').is(28))
 .values('name')

3. or(),逻辑或

只有一个条件时,and()or()的效果一样的。

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

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

// 包含边“created”或属性“age”为28的顶点的名字“name”
g.V().or(outE('created'), values('age').is(28)).values('name')

// 同上,放在where()里面的用法。包含边“created”或属性“age”为28的顶点的名字“name”
g.V().where(outE('created')
            .or()
            .values('age').is(28))
 .values('name')

4. not(),逻辑非

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

// 筛选出所有包含不少于两条(大于等于两条)“created”边的“person”的名字“name”
g.V().hasLabel('person').not(out('created').count().is(lt(2))).values('name')
// 获取所有最多只有一条“created”边并且年龄不等于28的“person”顶点)
g.V().hasLabel('person')
 .and(outE('created').count().is(lte(1)), 
      values("age").is(P.not(P.eq(28))))
 .values('name')

十一. 统计运算

Gremlin可以在Number类型的流(遍历器)上做简单的统计运算,包括计算总和、最大值、最小值、均值。

下面讲解实现上述功能的具体Step:

  • sum():将流上的所有的数字求和;
  • max():对流上的所有的数字求最大值;
  • min():对流上的所有的数字求最小值;
  • mean():将流上的所有的数字求均值;

这四种Step只能作用在Number类型的流上,在java里就是继承自java.lang.Number类。

// 计算所有“person”的“age”的总和
g.V().hasLabel('person').values('age').sum()

// 计算所有“person”的“created”出边数的总和,map将sum()函数应用到其内部的遍历
g.V().hasLabel('person').map(outE('created').count()).sum()

// 计算所有“person”的“age”中的最大值
g.V().hasLabel('person').values('age').max()

// 计算所有“person”的“created”出边数的最大值
g.V().hasLabel('person').map(outE('created').count()).max()

// 计算所有“person”的“age”中的最小值
g.V().hasLabel('person').values('age').min()

// 计算所有“person”的“created”出边数的最小值
g.V().hasLabel('person').map(outE('created').count()).min()

// 计算所有“person”的“age”的均值
g.V().hasLabel('person').values('age').mean()

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

十二. 数学运算

在Gremlin中有一个专门负责科学计算功能的step math()math() 不同于常见的函数组合和嵌套形式,提供了一种易于读取的基于字符串的数学处理器。

  • math() 支持by(),其中多个by() 按照在math()运算表达式中首次引用变量的顺序应用。
  • 保留变量_是指传入math()的当前遍历器对象。
math()`支持的运算符包括:`+`,`-`,`*`,`/`,`%`,`^

math()支持的内嵌函数包括:

  • abs: absolute value,绝对值
  • acos: arc cosine,反余弦
  • asin: arc sine,反正弦
  • atan: arc tangent,反正切
  • cbrt: cubic root,立方根
  • ceil: nearest upper integer,向上最接近的整数
  • cos: cosine,余弦
  • cosh: hyperbolic cosine,双曲余弦
  • exp: euler’s number raised to the power (e^x),以e为底的指数
  • floor: nearest lower integer,向下最近接的整数
  • log: logarithmus naturalis (base e),以e为底的对数
  • log10: logarithm (base 10),以10为底的对数
  • log2: logarithm (base 2),以2为底的对数
  • sin: sine,正弦
  • sinh: hyperbolic sine,双曲正弦
  • sqrt: square root,平方根
  • tan: tangent,正切
  • tanh: hyperbolic tangent,双曲正切
  • signum: signum function,签名功能
// 比如按照年龄段进行统计
g.V().hasLabel('person')
 .groupCount().by(values('age').math('floor(_/10)*10'))
 .order(local).by(values,desc)

你可能感兴趣的:(知识图谱,knowledge,graph)