读图数据库实战笔记04_路径与图变异

发布时间 2023-10-31 06:27:06作者: 躺柒

1. Groovy

1.1. Java编程语言的一个超集

1.2. Gremlin Console的一个特性是能和Groovy配合使用

1.2.1. Gremlin Console会自动地迭代结果

1.3. 从技术上说,Gremlin Console就是Groovy交互式解释器(read-eval-print loop,REPL)

1.3.1. 既可以作为一个独立的程序运行,也可以很容易地在其他程序中作为整体程序的一部分使用

2. 变异(mutation)

2.1. 简单地被理解为通过添加、修改或删除顶点、边和/或属性来改变图

2.2. 变异遍历或变异过程是在某种程度上改变图的内容或结构的操作

3. 添加顶点

3.1. INSERT INTO person (first_name) VALUES ('Dave');

3.2. 在图中添加顶点相当于在关系数据库中添加实体

3.3. 过程

3.3.1. 给定一个遍历源g

3.3.2. 添加一个新的顶点,类型是person

3.3.3. 在该顶点上添加一个属性,键是first_name,值是Dave

3.4. groovy

g.addV('person').property('first_name', 'Dave')
==>v[13]

3.4.1. 只需要为每一个addV()操作增加一个单一顶点

3.4.2. addV(label)

3.4.2.1. 在图中增加一个类型为label的顶点并返回这个新增顶点的引用

3.4.3. property(key, value)

3.4.3.1. 在顶点或边上增加一个属性

3.4.3.2. 该属性包含指定的key和value

3.4.3.3. 返回对进入该操作的顶点或边的引用

3.4.4. v[13]

3.4.4.1. 顶点的唯一的识别码

3.4.4.2. 这个ID值是根据数据库的当前状态内部生成的

3.4.4.2.1. 顶点的ID值应该被视为图数据库引擎的内部信息
3.4.4.2.2. 对数据库引擎等工具的内部信息,你应该保持足够的警惕
3.4.4.2.3. 为了应用程序的需要而在应用代码中使用它们则是极其危险的

3.4.4.3. Gremlin Server,在顶点上使用简单整数(32位),在边上使用长整数(64位)

3.4.4.4. 其他图数据库引擎可能会使用UUID或GUID

3.4.4.5. 虽然可以在代码中运用这些值,但最佳实践是不去使用它们

3.5. g.V().addV('person')

3.5.1. V()操作不仅代表图中的所有顶点,也会返回所有顶点

3.5.1.1. 下一个操作将在上一步操作输出的每个元素上执行

3.5.2. 图中的每一个现有顶点都会被添加一个person顶点

3.5.3. 在某些情况下,这可能是我们想要的结果

4. 添加边

4.1. 在关系数据库世界里,实体间的连接是通过外键隐式实现的

4.2. 在图的世界里,这些连接需要通过边来显式添加

4.3. 在添加边时,需要为它指定入顶点和出顶点

4.4. SELECT * FROM table1 WHERE id = (SELECT id1 FROM table2);

4.5. 过程

4.5.1. 给定一个遍历源g

4.5.2. 添加一条标签为friends的边

4.5.3. 分配边的出顶点是键为first_name、值为Ted的顶点

4.5.4. 分配边的入顶点是键为first_name、值为Hank的顶点

4.6. groovy

g.addE('friends').
  from(
    V().has('person', 'first_name', 'Ted')
  ).
  to(
    V().has('person', 'first_name', 'Hank')
  )
==>e[15][4-friends->6]

4.6.1. addE(label)

4.6.1.1. 添加一条标签为label的边

4.6.2. from(vertex)

4.6.2.1. 指定边从哪个顶点开始的调节器

4.6.3. to(vertex)

4.6.3.1. 指定边在哪个顶点结束的调节器

4.6.4. 操作调节器不能单独使用

4.6.5. 调节器来源于TinkerPop,但提供开始顶点和结束顶点的细节则是通用需求

5. 删除顶点

5.1. DELETE FROM person WHERE person_id = 13;

5.2. 过程

5.2.1. 给定一个遍历源g

5.2.2. 找到一个ID为13的顶点

5.2.3. 删除这个顶点

5.3. g.V(13).drop()

5.3.1. V(id)

5.3.1.1. 返回id指定的顶点

5.3.1.2. 这个id是由Gremlin Server(其选择的数据库)分配和维护的内部ID

5.3.2. drop()

5.3.2.1. 删除任何经过它的顶点、边或属性

5.3.2.2. 没有任何返回结果

5.3.2.2.1. 发挥作用之后,它不会向客户端返回任何消息

6. 删除边

6.1. 方式1

6.1.1. 如果删除开始顶点或结束顶点,那么任何与该顶点关联的边也会被删除,这是图数据库版本的引用完整性体现

6.2. 方式2

6.2.1. 显式地删除它们,留下开始顶点和结束顶点

6.3. 过程

6.3.1. 给定一个遍历源g

6.3.2. 找到一条ID为15的边

6.3.3. 删除这条边

6.4. g.E(15).drop()

6.4.1. 在TinkerPop中,g.E()的默认实现需要一个类型为Long而不是int的参数,在Java中要留意这一点

6.4.2. 几乎和删除顶点的语句一模一样

6.4.2.1. 这种相似的语法也昭示了顶点和边在图数据库中同等重要

7. 修改图

7.1. UPDATE person SET first_name = 'Dave' WHERE first_name = 'Dav';

7.2. 过程

7.2.1. 给定一个遍历源g

7.2.2. 找到一个键为first_name、值为Dav的顶点

7.2.3. 修改该顶点的属性,将其值改为Dave

7.3. groovy

g.V().has('person', 'first_name', 'Dav').
      property('first_name', 'Dave')
==>v[18]

8. 脚本变异

8.1. groovy

g.V().drop().iterate()
dave = g.addV('person').property('first_name', 'Dave').next()
josh = g.addV('person').property('first_name', 'Josh').next()
ted = g.addV('person').property('first_name', 'Ted').next()
hank = g.addV('person').property('first_name', 'Hank').next()
g.addE('friends').from(dave).to(ted).next()
g.addE('friends').from(dave).to(josh).next()
g.addE('friends').from(dave).to(hank).next()
g.addE('friends').from(josh).to(hank).next()
g.addE('friends').from(ted).to(josh).next()

8.2. Gremlin代码的第一行是g.V().drop().iterate(),用来清除图中的所有数据

8.3. iterate()操作和next()操作类似,都会触发遍历

8.4. iterate()操作不会返回结果,而next()操作会返回遍历的结果

8.5. 由于drop()操作不返回值,因此这里使用iterate()操作比next()操作更合适

8.6. dave = g.addV('person').property('first_name', 'Dave').next()

8.6.1. 要在添加边之前把相应的顶点创建好

8.6.2. 过程

8.6.2.1. 给定一个遍历源g

8.6.2.2. 添加一个含有标签person的新顶点

8.6.2.3. 为这个顶点添加一个键为first_name、值为Dave的属性

8.6.2.4. 执行这些操作,返回迭代中的第一个(下一个)项作为结果

8.6.3. 声明保存遍历结果的变量dave

8.6.3.1. 变量是图世界中另一种差别的源头

8.6.3.1.1. Cypher,不支持这种跨请求的查询
8.6.3.1.2. 甚至在支持TinkerPop的图数据库间的支持程度都不一样
8.6.3.1.3. 不管是Azure的CosmosDB还是Amazon Nepture,都没有这个功能
8.6.3.1.4. JanusGraph和DataStax Enterprise Graph则完全支持
8.6.3.1.5. 如果查询语言和数据库支持变量的话,我们推荐使用它们
8.6.3.1.6. 变量可以极大地简化某些操作
8.6.3.1.6.1. 把添加顶点和边的操作串联在一起

8.6.3.2. 当要把结果赋值给一个变量时,则不得不包含终端操作

8.6.3.3. 否则,整个迭代集会被分配到变量中,而不只是迭代集里的那些结果

8.6.3.4. 这不是一个幂等的操作

8.6.4. next()

8.6.4.1. 一个终端操作,它从上一个操作获得迭代遍历源,迭代一次,然后返回迭代中的第一个或下一个项

8.6.4.2. 类似于iterate()的终端操作

8.6.4.3. 可以把它想成一个强制遍历循环的操作

8.7. g.addE('friends').from(dave).to(ted)

8.7.1. 给定一个遍历源g

8.7.2. 增加一条标签为friends的边

8.7.3. 出顶点指向dave变量

8.7.4. 入顶点指向ted变量

9. 链式变异

9.1. 在图数据库中,变异可以链接在一起,以同时执行多个变更

9.2. groovy

g.addE('friends').from(dave).to(josh).
  addE('friends').from(dave).to(hank).
  addE('friends').from(josh).to(hank).
  addE('friends').from(ted).to(josh).iterate()

9.3. 要在一个语句中包含复杂的操作,链接操作是Gremlin中的基本策略

9.3.1. 每一个操作从上一个操作的输出获取数据,并在处理后将该数据交给下一个操作

9.3.2. 对于熟悉函数式编程的朋友来说,这一切都似曾相识

9.4. 图遍历中的变异操作允许把多个变异操作链接成一个操作,而SQL做不到这一点

10. 路径(path)

10.1. 在遍历中从开始顶点访问到结束顶点之间的所有顶点和边的列表

10.2. 不仅告诉我们两个顶点是连接的,而且展示了它们之间的所有中间元素

10.3. 路径描述了从开始顶点到结束顶点的一系列遍历操作

10.4. 路径代表一系列连接两个元素的顶点和边

10.5. 意味着我们不仅能发现两个顶点是相互连接的,而且能确定如何从起点到达终点

10.6. groovy

g.V().has('person', 'first_name', 'Ted').
  until(has('person', 'first_name', 'Denise')).
  repeat(
    both('friends')
  ).path()

10.6.1. path()

10.6.1.1. 当遍历执行时,返回指定遍历器访问顶点(某些时候还有边)的历史

10.6.1.2. 在Gremlin中使用path()操作会增加服务器的资源消耗,因为每个遍历器都需要维护自己访问的所有历史记录

10.6.1.3. 只在需要所有路径数据时使用path()

10.7. groovy

Script evaluation exceeded the configured 'scriptEvaluationTimeout' threshold
     of 30000 ms or evaluation was otherwise cancelled directly for request
     [g.V().has('person', 'first_name', 'Ted').
  until(has('person', 'first_name', 'Denise')).
  repeat(
    both('friends')
  ).path()]
Type ':help' or ':h' for help.

10.7.1. 遍历超时了

10.7.2. 图中生成了一个环

10.7.2.1. 环是图论中的一个概念,指的是图中顶点和边的路径包含一个或多个能到达自己的顶点

10.7.2.2. 环指的是含有重复顶点的路径,通常会在图遍历过程中引起长时间运行的循环与寻路查询

10.7.3. :clear命令清除缓存并重新启动遍历

11. 简单路径

11.1. 不会在任何一个顶点上重复的路径

11.2. 在简单路径中只会得到非环的结果

11.2.1. 简单路径就是图中不含重复顶点的路径

11.3. 每个遍历器会维护它访问的所有项的历史

11.3.1. 当碰到一个曾经访问过的项时,它就知道这个元素在环中,并把它剔除

11.3.2. 只有那些不含环的遍历器会继续完成遍历工作

11.4. groovy

g.V().has('person', 'first_name', 'Ted').
  until(has('person', 'first_name', 'Denise')).
  repeat(
    both('friends').simplePath()
  ).path()
==>path[v[4], v[0], v[15], v[19]]
==>path[v[4], v[0], v[13], v[19]]
==>path[v[4], v[2], v[0], v[15], v[19]]
==>path[v[4], v[2], v[0], v[13], v[19]]
==>path[v[4], v[0], v[15], v[17], v[19]]
==>path[v[4], v[0], v[15], v[13], v[19]]
==>path[v[4], v[0], v[13], v[15], v[19]]
==>path[v[4], v[2], v[6], v[0], v[15], v[19]]
==>path[v[4], v[2], v[6], v[0], v[13], v[19]]
==>path[v[4], v[2], v[0], v[15], v[17], v[19]]
==>path[v[4], v[2], v[0], v[15], v[13], v[19]]
==>path[v[4], v[2], v[0], v[13], v[15], v[19]]
==>path[v[4], v[0], v[13], v[15], v[17], v[19]]
==>path[v[4], v[2], v[6], v[0], v[15], v[17], v[19]]
==>path[v[4], v[2], v[6], v[0], v[15], v[13], v[19]]
==>path[v[4], v[2], v[6], v[0], v[13], v[15], v[19]]
==>path[v[4], v[2], v[0], v[13], v[15], v[17], v[19]]
==>path[v[4], v[2], v[6], v[0], v[13], v[15], v[17], v[19]]

11.4.1. simplePath()

11.4.1.1. 筛选掉遍历中被重复访问的顶点

11.4.1.2. 如果把simplePath()放在遍历末尾,它就在循环逻辑之外了,遍历器会被困在环里,无法离开

11.4.1.3. 就像在Java里创建一个for循环,但把计数器变量放在for循环之外

12. 遍历和筛选边

12.1. 从顶点走到边,再从边走到顶点。必须显式遍历到边,并显式从边离开

12.2. 边可以直接被遍历和筛选,不需要遍历到相邻的顶点

12.3. InE(label)

12.3.1. 从当前顶点遍历到相邻的入边

12.4. outE(label)

12.4.1. 从当前顶点遍历到相邻的出边

12.5. bothE(label)

12.5.1. 从当前顶点遍历到相邻边,不考虑方向

12.6. 如果指定了标签,只遍历到符合筛选条件的边

12.7. 每个E操作都从一个顶点开始,遍历到一条边,然后停留在这条边上

12.7.1. 这些操作都结束于边,而不是相邻的顶点

12.8. inV()

12.8.1. 从当前边遍历到入顶点

12.8.2. 通常和outE()操作搭配使用

12.8.2.1. 组合来完成到相邻顶点的遍历

12.9. outV()

12.9.1. 从当前边遍历到出顶点

12.9.2. 通常和inE()操作搭配使用

12.9.2.1. 组合来完成到相邻顶点的遍历

12.10. otherV()

12.10.1. 遍历到不是出顶点的那个顶点(如另一个顶点)

12.10.2. 通常和bothE()操作搭配使用

12.10.2.1. 轻松地带到“另一个顶点”上,不是遍历出发的那个顶点

12.11. bothV()

12.11.1. 从当前边遍历到两个相邻的顶点

12.11.2. 极少用到

12.12. groovy

g.V().has('person','first_name','Dave').
  bothE('works_with').otherV().values('first_name')
==>Ted
==>Josh
==>Hank
==>Kelly
==>Denise

12.13. groovy

g.V().has('person', 'first_name','Dave').
  both('works_with').values('first_name')
==>Ted
==>Josh
==>Hank
==>Kelly
==>Denise

12.14. groovy

g.V().has('person', 'first_name', 'Ted').
  until(has('person', 'first_name', 'Denise')).
  repeat(
    bothE('works_with').otherV().simplePath()
  ).path()
==>path[v[4], e[29][0-works_with->4], v[0], e[33][0-works_with->19], v[19]]
==>path[v[4], e[29][0-works_with->4], v[0], e[32][0-works_with->13], v[13],
     e[34][19-works_with->13], v[19]]
==>path[v[4], e[30][2-works_with->4], v[2], e[28][0-works_with->2], v[0],
     e[33][0-works_with->19], v[19]]
==>path[v[4], e[30][2-works_with->4], v[2], e[28][0-works_with->2], v[0],
     e[[32][0-works_with->13], v[13], e[34][19-works_with->13], v[19]]

12.14.1. bothE().otherV()遍历模式来显式地遍历到边上

12.14.2. 在路径结果中包括边

12.14.3. 航空交通路线

12.14.3.1. 顶点代表机场,边代表机场间的航班

13. 通过属性筛选边

13.1. 基于时间的筛选

13.2. 基于权重的筛选

13.3. groovy

g.V().has('person','first_name','Dave').
  bothE('works_with').has('end_year',lte(2018)).
  otherV().
  values('first_name')
==>Josh
==>Ted
==>Hank

13.3.1. 可以基于边的属性进行筛选

13.3.2. 使用一些断言操作,如代表“小于或等于”的lte()

13.3.3. 对于比单值匹配更复杂的操作,断言操作是非常好用的管理工具

14. 边的计数

14.1. g.V().bothE().count()

14.2. g.V().both().count()

14.2.1. 通过both()操作来计数通常代价更高昂

15. 反规范化(denormalization)

15.1. 把经常访问的顶点属性复制到相邻的边上

15.2. 它对于某些以读为主的活动类型特别有帮助

15.3. 会带来维护同一个属性值的两份副本的开销

15.4. 维护数据的多个副本就是反规范化,不管使用关系数据库还是图数据库,反规范化都会带来这种额外的开销