如何更好的分析潜在人脉?聊聊华为云图引擎GES的Cypher子查询
摘要:本文以华为云图引擎 GES 为例,来介绍如何使用图查询语言 Cypher 表达一些需要做数据局部遍历的场景。
本文分享自华为云社区《使用 Cypher 子查询进行图探索 -- 以华为云图引擎 GES 为例》,作者:蜉蝣与海。
在图数据库/图计算领域,很多查询可以使用图查询语言Cypher、Gremlin或者指令式API进行表达,如多跳过滤、全局检索以及对过滤后的结果进行聚集排序等操作。然而有些查询不是那么容易表达,常常需要对图中的一组数据去做局部遍历,例如在社交网络(人-人,人-兴趣的关联网络)场景中,常常涉及以下场景:
- 朋友推荐:看看小明的朋友的朋友中,哪些不是小明的朋友,进而推荐给小明。
- 潜在二度人脉分析:选取一组点,每个点代表一个人,在他们朋友的朋友中,统计他们各自有多少不认识的男性朋友和女性朋友。
- 兴趣推荐A:兴趣爱好也是社交网络中的点,看看小明的朋友有哪些兴趣爱好(人-INTEREST-兴趣),从每个朋友的兴趣爱好中选取至多N个兴趣爱好推荐给小明。
- 兴趣推荐B:看小明有哪些朋友还没有录入兴趣爱好,允许小明把自己的兴趣爱好推荐给他们。
这些查询往往只关注图中的某个局部,对局部进行多跳查询,且局部上往往有类似下列限制:
- 数量限制:例如兴趣推荐A场景中,限制了每个朋友的兴趣数目,而不是总数目。
- 条件限制:例如朋友推荐场景中,“哪些不是小明的朋友”需要先查询小明和朋友的朋友间有没有边,并将结果作为查询条件输入用来过滤。
在查询语言Cypher中,常常使用子查询来解决这类问题。本文会以华为云图引擎GES为例(图引擎版本>=2.3.6),来介绍如何使用Cypher表达上述场景。
注: 本文同步发布至华为云AI Gallery,文中所有代码皆可以在AI Gallery上运行:【AI Gallery】使用Cypher子查询进行图探索 – 以华为云图引擎GES为例。
阅读前准备
基础知识
阅读前需要了解如下基础知识
- Cypher查询语言的基本结构:
- 关于Cypher样例语句,可以参考:图引擎服务帮助文档-业务面API-Cypher-基本操作和兼容性
- 关于Cypher的文法说明,可以参考openCypher 9官方页面。
- Cypher的列表表达式:
- 华为云图引擎GES支持的列表表达式,可以参考图引擎服务帮助文档-业务面API-Cypher-支持的表达式,函数及过程
- Neo4j 3.5 Cypher Manual - Lists - List Comprehension
- 对本文中使用到的列表表达式,提前做下述解释:
下方三个小节会指导如何配置一个GES实例并使用notebook连接GES服务进而做查询演示。如果你只想了解如何编写查询语句,对输入的Cypher查询获取返回结果没有需求,可以直接跳过下方三个小节。
本文使用的数据集
本教程使用LDBC-SF0.1社交数据集中截选的人物关系数据集,数据集可以从此处下载。下载后需要在GES中创建图并导入数据集,详细指导流程可参见华为图引擎文档-快速入门和华为云图引擎服务 GES 实战——创图。
如何调用GES的Cypher API
GES官网帮助文档上有GES Cypher的API,为了方便用户调用,API设计为基于http/https请求,响应体的设计也兼容的neo4j的json格式。这里放置一下链接执行Cypher查询。调用API时需要将Token输入请求头中进行鉴权,有关Token的获取问题请参考业务面API认证鉴权。
本文会使用ges4jupyter工具脚本进行相关查询的演示,该脚本中封装了刚刚提到的鉴权&Cypher查询API,并对结果进行了一些处理,提供了相关可视化的能力。
本文使用的代码包
ges4jupyter是jupyter连接GES服务的工具文件。文件中封装了使用 GES 查询的预置条件,包括配置相关参数和对所调用 API 接口的封装,如果你对这些不感兴趣,可直接运行而不需要了解细节,这对理解后续具体查询没有影响。本文的所有语句请求都会访问一个GES实例并得到实际的响应。
import moxing as mox
mox.file.copy('obs://obs-aigallery-zc/GES/ges4jupyter/beta/ges4jupyter.py', 'ges4jupyter.py')
mox.file.copy('obs://obs-aigallery-zc/GES/ges4jupyter/beta/ges4jupyter.html', 'ges4jupyter.html')
GESConfig的参数都是与调用 GES 服务有关的参数,依次为“公网访问地址”、“项目ID”、“图名”、“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“所属项目”,其获取方式可参考调用 GES 服务业务面 API 相关参数的获取。这里通过read_csv_config方法从配置文件中读取这些信息。如果没有配置文件,可以根据自己的需要补充下列字段。对于开启了https安全模式的图实例,参数port的值为443。
from ges4jupyter import GESConfig, GES4Jupyter, read_csv_config
eip = ''
project_id = ''
graph_name = ''
iam_url = ''
user_name = ''
password = ''
domain_name = ''
project_name = ''
port = 80
eip, project_id, graph_name, iam_url, user_name, password, domain_name, project_name, port = read_csv_config('cn_north_4_graph.csv')
config = GESConfig(eip, project_id, graph_name,
iam_url = iam_url,
user_name = user_name,
password = password,
domain_name = domain_name,
project_name = project_name,
port = port)
ges_util = GES4Jupyter(config, True);
首先在GES中创建索引,这有利于后续查询加速。
import time
def wait_job_finish(util, job_id, max_loop):
job_result = util.get_job(job_id)
if 'errorCode' not in job_result:
for i in range(max_loop):
if job_result['status'] == 'success':
break
else:
time.sleep(1)
job_result = util.get_job(job_id)
print(job_result) job_id = ges_util.build_vertex_index()
wait_job_finish(ges_util, job_id, 100)
job_id = ges_util.build_edge_index()
wait_job_finish(ges_util, job_id, 100)
可以使用下列语句查看schema信息:
import time
body = ges_util.generate_schema_structure()
job_id = body["jobId"]
print('开始构造schema结构:')
wait_job_finish(ges_util, job_id, 100)
print('schema结构构造完成')
cypher_result = ges_util.cypher_query("call db.schema()",formats=['row','graph']);
ges_util.format_cypher_result(cypher_result, candidate_title = ['description', 'name'])
如图是本文使用的数据集的schema,主要包括下列类型的点边:
使用子查询
一般来说,使用Cypher查询朋友的朋友是相对容易的,下列语句演示了如何查询顶点p367朋友的朋友。
match (n)-[:KNOWS]->(a)-[:KNOWS]->(b) where id(n)='p367' return distinct b
然而,使用一般的Cypher语义,从朋友的朋友中移除所有的朋友,表达朋友推荐场景中的“朋友的朋友而非我的朋友”却很困难。文章如何使用GES进行社交关系考据?—GES查询能力介绍中,描述了一种常规的查询语句写法:
match (n)-[:KNOWS]->(a) where id(n)='p367' with n, collect(a) as neighbor
match (n)-[:KNOWS]->(a)-[:KNOWS]->(b)
where not (b in neighbor)
return b
由于cypher的结果是使用行(Row)组织数据,所有的计算以“行”作为单元进行,如果要进行过滤,只能进行行内过滤。所以上述语句第一步,先通过collect(a),将“朋友”这个集合组织到了一行里,而后才能将collect(a)作为过滤条件,进行二次查询。
将子查询作为查询条件
在GES 2.3.6版本,实现了子查询能力,支持Neo4j中的SemiApply算子,该算子支持类似于下列语句的运行,使得查询更为简洁:
match (n) where id(n)='p367'
match (n)-[:KNOWS*2..2]->(b)
where not (n)-[:KNOWS]->(b)
return id(b) limit 10
cypher_result = ges_util.cypher_query("""
match (n) where id(n)='p367'
match (n)-[:KNOWS*2..2]->(b) where not (n)-[:KNOWS]->(b)
return id(b) limit 10""",formats=['row','graph']);
ges_util.format_cypher_result(cypher_result)
注意到这里where条件后面跟从的不是一个一般的条件表达式,不是大于小于这样的比较运算,在条件运算not后跟随了一个图模式(Graph Pattern),整个where条件表示“不存在从顶点n连向顶点b,且label为KNOWS的边”。这样的表达方式使得整条查询语句看起来更为简洁。
也可以使用explain查看其查询计划,可以看到是AntiSemiApply在发挥作用。这里条件查询主要包含两个算子:
- SemiApply: 用于支撑“where (n)-[:KNOWS]->(b)”这样的条件,表示对应的查询模式存在。
- AntiSemiApply:用于支撑“where not (n)-[:KNOWS]->(b)”这样的条件,表示对应的查询模式不存在。
这两个算子对每个左子树生成的结果,都去检查右子树是否会/不会产生满足条件的结果,并将右子树的结果作为过滤条件,辅助左子树的结果过滤。
通过这两个算子,即可实现简单的条件子查询。
cypher_result = ges_util.cypher_query("""explain
match (n) where id(n)='p367'
match (n)-[:KNOWS*2..2]->(b) where not (n)-[:KNOWS]->(b)
return id(b) limit 10""",formats=['row','graph']);
ges_util.format_cypher_result(cypher_result)
子查询作为条件,也可以用来描述兴趣推荐B场景:看小明有哪些朋友还没有录入兴趣爱好,允许小明把自己的兴趣爱好推荐给他们。
match (n:Person) where id(n)='p933'
match (n)-[r]->(m) where not (m)-[:HAS_INTEREST]-() return id(m)
将子查询作为中间结果
此外,还可以将子查询作为中间结果,朋友推荐场景下,cypher语句还可以这么写:
match (n) where id(n)='p367'
with [(n)-[:KNOWS*2..2]->(b)|id(b)] as hop2, [(n)-[:KNOWS]->(b)|id(b)] as hop1
return [x in hop2 where not x in hop1|x] limit 10
在这条查询语句中,Graph Pattern出现在了with子句中,用于收集某个点的多跳结果。
另外采用类似的写法还可以筛选三度好友中“我不认识的人”的数目,示例如下:
match (n) where id(n)='p367'
with [(n)-[:KNOWS*3..3]->(b)|id(b)] as hop3, [(n)-[:KNOWS*1..2]->(b)|id(b)] as hop2
return size([x in hop3 where not x in hop2|x])
cypher_result = ges_util.cypher_query("""
match (n) where id(n)='p367'
with [(n)-[:KNOWS*3..3]->(b)|id(b)] as hop3, [(n)-[:KNOWS*1..2]->(b)|id(b)] as hop2
return size([x in hop3 where not x in hop2|x])""",formats=['row','graph']);
ges_util.format_cypher_result(cypher_result, boxHeight=200)
同时这种子查询后续步骤也可以跟随一些过滤条件,进行各类统计操作,如上述提到的潜在二度人脉分析:
match (n:Person) where id(n) in ['p367','p13194139534836','p932','p4398046512206','p6597069767359']
with n, [(n)-[:KNOWS*2..2]->(m) where not (n)-->(m)|m] as recSet
return id(n) as key,
size([x in recSet where x.gender='male']) as maleNumber,
size([x in recSet where x.gender='female']) as femaleNumber
cypher_result = ges_util.cypher_query("""
match (n:Person) where id(n) in ['p367','p13194139534836','p932','p4398046512206','p6597069767359']
with n, [(n)-[:KNOWS*2..2]->(m) where not (n)-->(m)|m] as recSet
return id(n), size([x in recSet where x.gender='male']),size([x in recSet where x.gender='female'])
""",formats=['row','graph']);
ges_util.format_cypher_result(cypher_result, boxHeight=200)
下列元素出现在with子句中,描述了一个子查询:
[(n)-[:KNOWS*2..2]->(m) where some-condition|m] as recSet
这里会对每个遍历到的n,都进行二跳查询, 取二跳查询的末端节点m,然后组装成一个列表。
注意到where条件中,使用了刚刚提到的条件子查询:
where not (n)-->(m)
这里条件使用where条件,对子查询的结果进行了过滤,且过滤时,是将一个Graph Pattern作为的过滤条件,最后使用竖线进行投影。
在return子句中,使用了Cypher中List Comprehension的语法,进行列表过滤,并获取大小:
return id(n) as key,
size([x in recSet where x.gender='male']) as maleNumber,
size([x in recSet where x.gender='female']) as femaleNumber
支撑子查询结果作为中间结果的,是RollUpApply算子,可以通过explain看到其在查询计划中发挥价值:
cypher_result = ges_util.cypher_query("""explain
match (n:Person) where id(n) in ['p367','p13194139534836','p932','p4398046512206','p6597069767359']
return n, [(n)-[:KNOWS*2..2]->(m) where not (n)-->(m)|m] as recSet""",formats=['row','graph']);
ges_util.format_cypher_result(cypher_result, boxHeight=200)
对每个左子树生成的结果(这里是 (n:Person))都会作为变量输入,并执行右子树,将右子树的结果打包返回为 list。
此外还可以限制子查询的数目,对查询进行 PerNodeLimit(单点跳出限制:每个点每层只能向外跳出限定个数的顶点)。
例如兴趣推荐 A 场景中,看看小明的朋友有哪些兴趣爱好(人 - INTEREST - 兴趣),从每个朋友的兴趣爱好中选取至多 N 个兴趣爱好推荐给小明。
match (n:Person) where id(n)='p367'
match (n)-[r]->(m)
return [(m)-[:HAS_INTEREST]-(a)|a][0..3]
为了可视化演示效果,可视化时同步打印了“朋友”和“INTEREST”边。
同样的,也可以使用RollUpApply+Limit对每跳做PerNodeLimit,例如统计和小明的朋友有共同兴趣爱好的朋友,每个顶点每跳最多找3个点,最后一跳每个点最多找1个点:
match (n:Person) where id(n)='p367'
match (n)-[r]->(m) with m limit 3
with m,[(m)<-[r1:HAS_INTEREST]-(a)|a][0..3] as interests
unwind interests as interest
with interest, [(interest)-[r1:HAS_INTEREST]->(a) where not (a)--(m)|[r1,a]][0..1] as soulMate
return *
其他子查询
使用with也可以实现其他子查询任务,例如上一跳的查询结果经过limit限制后输入下一跳,成为查询条件:
match (n:Person) where id(n) in ['p367','p13194139534836','p932','p4398046512206','p6597069767359']
with n limit 10
match (m:Person{lastName:n.lastName}) return n.lastName, m.firstName
使用explain也可以看到其查询计划:
cypher_result = ges_util.cypher_query("""explain
match (n:Person) where id(n) in ['p367','p13194139534836','p932','p4398046512206','p6597069767359']
with n limit 10
match (m:Person{lastName:n.lastName}) return n.lastName, m.firstName""",formats=['row','graph']);
ges_util.format_cypher_result(cypher_result)
由于不同的n,其n.lastName的值是不固定的,所以需要针对每个n,去做match (m:Person{lastName:n.lastName})
这样的查询,因此需要使用Apply子查询算子支撑这样的语句。
总结借助子查询进行局部遍历是图查询中的常用操作,将子查询作为过滤条件或者中间结果辅助查询,可以满足某些业务场景下对查询局部有限制的诉求,
如文中提到的社交网络分析,再如股权关系中穿透层数分析、装备制造和配置管理(IT设备管理)领域依赖识别和变更影响分析等。
此外,由于Cypher以行的形式组织数据,某些情况下使用子查询可以节省中间结果产生,加速Cypher查询的执行。
当然,使用更高效的API(如GES产品中有多跳过滤API)或者使用非行存的查询执行引擎也是可选的解决方案。
如何更好的分析潜在人脉?聊聊华为云图引擎GES的Cypher子查询的更多相关文章
- sql 语句 嵌套子查询 执行顺序分析
--创建测试数据create table Student(S# varchar(10),Sname nvarchar(10),Sage datetime,Ssex nvarchar(10))inser ...
- hive 子查询特别分析
Hive只支持在FROM子句中使用子查询,子查询必须有名字,并且列必须唯一:SELECT ... FROM(subquery) name ... 确认下是否一定要求列必须唯一? 建表语句 ...
- mysql(4)—— 表连接查询与where后使用子查询的性能分析。
子查询就是在一条查询语句中还有其它的查询语句,主查询得到的结果依赖于子查询的结果. 子查询的子语句可以在一条sql语句的FROM,JOIN,和WHERE后面,本文主要针对在WHERE后面使用子查询与表 ...
- MySQL 使用profile分析慢sql,group left join效率高于子查询
MySQL 使用profile分析慢sql,group left join效率高于子查询 http://blog.csdn.net/mchdba/article/details/54380221 -- ...
- 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!
目录 目录 string基础 Java String 类 创建字符串 StringDemo.java 文件代码: String基本用法 创建String对象的常用方法 String中常用的方法,用法如 ...
- jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理
这一节要分析的东东比较复杂,篇幅会比较大,也不知道我描述后能不能让人看明白.这部分的源码我第一次看的时候也比较吃力,现在重头看一遍,再分析一遍,看能否查缺补漏. 看这一部分的源码需要有一个完整的概念后 ...
- jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——总结与性能分析
Sizzle引擎的主体部分已经分析完毕了,今天为这部分划一个句号. a. Sizzle解析流程总结 是时候该做一个总结了.Sizzle解析的流程已经一目了然了. 1.选择器进入Sizzle( sele ...
- jQuery源码分析系列(三)Sizzle选择器引擎-下
选择函数:select() 看到select()函数,if(match.length === 1){}存在的意义是尽量简化执行步骤,避免compile()函数的调用. 简化操作同样根据tokenize ...
- jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——一些有用的Sizzle API
说一下Sizzle中零碎的API.这些API有的被jQuery接管,直接使用jQuery.xxx就可以使用,有的没有被接管,如果要在jQuery中使用,使用方法是jQuery.find.xxx. 具体 ...
- jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——词法解析
jQuery源码9600多行,而Sizzle引擎就独占近2000行,占了1/5.Sizzle引擎.jQuery事件机制.ajax是整个jQuery的核心,也是jQuery技术精华的体现.里面的有些策略 ...
随机推荐
- CF1343C
题目简化和分析: 给您一个序列,您要在其中选择若干个数使得: 相邻两数异号 长度最大,总和最大 我们可以牢牢抓住长度且总和最大,这一特性. 说明我们必须在每一个连续的同号的子串中被迫选择最大的,以满足 ...
- Newstar CTF 2023
WEEK1 PWN 1.ezshellcode 直接sendline(shellcode)即可 exp: from pwn import * p = remote("node4.buuoj. ...
- 面试官:SOA 和微服务的区别?这回终于搞清楚了!
https://developer.aliyun.com/article/839526 简介: 如果我们打开支付宝首页,去看我们的余额,它会展示你的总资产,昨日收益.累计收益等信息.假如这个页面所展示 ...
- Ansible自动化部署工具-组件及语法介绍
大家好,我是蓝胖子,我认为自动化运维要做的事情就是把运维过程中的某些步骤流程化,代码化,这样在以后执行类似的操作的时候就可以解放双手了,让程序自动完成.避免出错,Ansible就是这方面非常好用的工具 ...
- Python 利用pandas和matplotlib绘制堆叠柱状图
在数据可视化中,堆叠柱状图是一种常用的图表类型,它能够清晰地展示多个类别的数据,并突出显示每个类别中各部分的总量和组成比例.本文将演示如何使用 Python 的 pandas 和 matplotlib ...
- 牛客多校第一场 A. Alice and Bob (暴力SG)
题目大概 有两堆石子,有两个人拿,一个人从一堆中拿\(k\)个,那么就必须从另一堆中拿\(s*k\)个,Alice先拿,问是否必赢. 解题: 数据不大,看到前\(20\)名队伍没有推结论做的..除了打 ...
- 基于资源编排服务(ROS)实现存量资源的IaC化
背景 如今,基础设施即代码(Infrastructure as code,IaC)是云资源管理和编排的趋势,基于 IaC 的管理模式,在提升云资源自动化管理能力,降低管理成本的同时,可以大大降低云资源 ...
- 探索 Web API:SpeechSynthesis 与文本语言转换技术
一.引言 随着科技的不断发展,人机交互的方式也在不断演变.语音识别和合成技术在人工智能领域中具有重要地位,它们为残障人士和日常生活中的各种场景提供了便利.Web API 是 Web 应用程序接口的一种 ...
- java如何导入导出excel
在Java中,可以使用多种方式导入和导出Excel文件.下面将详细介绍几种常见的方法及其实现步骤: 1. Apache POI库: Apache POI是一个开源的Java库,提供了许多类和方法用于处 ...
- 【I/O设备】显示设备 Display
显示设备 电信号→视觉信号 属于软复制输出设备:输出内容不能长期保存 显示内容分为:字符.图形.图像 按显示器件分类:CRT.LCD.OLED等 (PD.LED.ELD.ECD.EPID) 按显示原理 ...