常见算子使用_基于Python的Cypher算子【1】

====================================================

你最近在干嘛?写SQL,哦SQL_boy

====================================================

怎么说呢,最近遇到了需要在python中灵活地在cypher语句里面装载和弃用关系结构、聚合函数、条件筛选的需求,然后就开始着手考虑构建一系列地cypher算子,但是吧,感觉似乎又要用到正则表达式啥的老麻烦了,就这么想着想着今天灵感突然爆发,想起之前写的LinQ(C#)语句,那个让我在多少个夜里感慨:“既生SQL何生LinQ”的辣鸡语句竟然在跨语言灵活装载sql问题上能发挥这么大作用??抱着试一试的心态开始整理整个构造过程:

1.业务范围

鉴于是基于图数据库构建的,因此作为知识沉淀的数据库,其实更常用的业务是在各种搜索的问题上,因而本文所有的内容都聚焦于cypher的搜索算子的构建上;另外,鉴于双实体关系是最常见的搜索/推理关系,因而本文将最初目光锁定在双实体多关系的问题上。

2.业务痛点

①同一结果不同cypher造成代码可读性降低

每当遇到一个新的业务就需要从头构建cypher,并且构造过程中有多种完成方式,很容易造成同一任务细微不同格式却需要重新构造cypher数据,比方说以下两个cypher语句:

1. MATCH (p1:Person{name:'Ay'})-[r:knows]->(p2:Person{name:'John'}) RETURN p1,r,p2
2. MATCH (p1)-[r:knows]->(p2) WHERE p1.name="Ay" and p2.name="John" RETURN p1,r,p2

表达的都是查找属性name="Ay"的实体与属性name="John"的实体之间认识的关系,但是前者直接通过index查询后者则通过条件筛选,因而最终写出来的cypher语句也就不同,容易使得同类业务代码未被察觉。

②代码可扩展性差

若同样的业务有不同的需求,比方说,根据用户的输入,我们需要回复不同的回答时:

以:“John认识多少人?”和“John认识谁?”的python-py2neo为例:

myGraph=py2neo.Graph(host,user,pwd)
if "多少" in inputStr:#判断问的是不是数字
    data=myGraph.run("MATCH (p1:Person{name:"John"})-[r:knows]->(p2) return COUNT(p2)").data()
elif "谁" in inputStr:#判断问的是不是具体的人
    data=myGraph.run("MATCH (p1:Person{name:"John"})-[r:knows]->(p2) return p2").data()

根据上述代码可知,当inputStr为“John认识多少人?”时,data返回的内容为2,反之为具体的人的列表,但是这样的结果就造成了当条件变多时就需要写大量的cypher去完成业务,增加开发及后期维护的难度。

综上两点(还有别的再补充),我决定开发一个易读的可扩展的cypher算子工具。

3.业务场景分解

我们首先回顾一个比较复杂的cypher:

MATCH (p1)-[r:knows]->(p2) 
WHERE p1.name="John" 
return p1,r,p2,COUNT(p2)

我们可以看到一个比较复杂的cypher语句主要由Match/Where/Return三个部分构成,Match构造图数据库的子图,WHERE可以加设条件,return既可以返回所需的实体/关系也可以返回相应的聚合函数

4.产品需求

因此基于以上分解,我们可以将我们的算子输入工具分为以下几个部分:

{
    "match":{
        "sub":{},#{"IDName":"","IDValue":"","nickName":""}
        "rel":{},#{"relName":"","nickName":""}
        "obj":{},#{"IDName":"","IDValue":"","nickName":""}
    },
    "where":"",
    "return":[],
}

即match中输入主谓宾,其中为了实现多关系,"rel"可以支持dict和list两种数据结构,

where为条件

return为返回的内容

基于以上数据,我们第一期希望能完成以下任务:

  1. 用户可通过更加pythonic的方式对不同的cypher业务进行区分;
  2. cypher细节函数可通过简单且明显的增删进行;
  3. 用户可查看各个算子最终生成的cypher也可直接运行
  4. 通过pipeline的方式构造算子
  5. 最终结果通过dataframe的形式展示

5.工具模块

当前工具暂定名为CqlFormer分如下几个模块,分别以函数形式表现出来,相应功能一并列出:

CqlFormer:

__init__      #初始化实例及neo4j数据库

getSub        #获取关系起始实体
getObj        #获取关系终点实体
getRel        #获取关系
getTri        #获取三元组
getCon        #获取筛选条件(待开发)
getReturn     #获取返回内容

outputJson    #以json格式输出
outputCypher  #以Cypher格式输出

run           #运行算子结果

相关参数如下表所示:

8.产品基本使用方式

STEP 1:构建CqlFormer对象

myCF=CqlFormer()

STEP 2:构建基本查询图

myCF.getRel(["knows"]).getObj(name="Amy")

上图表示查询认识Amy的人,相对应的查询图大致形状如下:

bf6979168dd6c93e2c05dea6e93e8043.png

根据上述基本查询图可推理大致的查询内容,于是,我们需要在getReturn中确定下来我们需要返回的是什么(例如:认识Amy的人有谁?认识Amy的人有多少个?认识Amy的人平均多少岁?认识Amy的人都来自哪些国家?等)。

STEP 3:确定返回内容

myCF.getRel(["knows"]).getObj(name="Amy").getReturn()

以上默认返回主谓宾

STEP 4:检查查询内容

可选择输出json查看是否所有对象都已就为,也可输出Cypher确定语法是否正确

myCF.getRel(["knows"]).getObj(name="Amy").getReturn().outputJson() #获取json
myCF.getRel(["knows"]).getObj(name="Amy").getReturn().outputCypher() #获取Cypher

所得结果分别如下:

Json:
{
 'match': 
    {'sub': {'nickName': 'sub8643853489963341526', 'IDName': '', 'IDValue': ''}, 
     'rel': {'relName': 'knows', 'nickName': 'rel7201487519220280229'}, 
     'obj': {'IDName': 'name', 'IDValue': 'Amy', 'nickName': 'obj2517711945240361104'}}, 
 'where': '', 
 'return': ['sub8643853489963341526', 'rel7201487519220280229', 'obj2517711945240361104']
}
Cypher:
MATCH (sub8643853489963341526)-[rel7201487519220280229:knows]->(obj2517711945240361104) 
WHERE obj2517711945240361104.name='Amy' 
RETURN sub8643853489963341526,rel7201487519220280229,obj2517711945240361104

STEP 5:运行Cypher(run)

myCF.getRel(["knows"]).getObj(name="Amy").getReturn().run()

所得结果如下:

54c2743482472962740785704a7436ee.png
默认返回["s","r","o"]

9.产品功能演示

相关代码已上传至github:

https://github.com/Timaos123/CypherFormer​github.com

首先把github的文件中的CqlFormer.py下载至自己的项目目录中,并在代码中调用该module:

from CqlFormer import CqlFormer

实体化CqlFormer,可以在这里输入自己的host/user/pwd:

myCF=CqlFormer(host,user,pwd)

假设我们的图数据库结构图下图所示:

f779a8acab3ae0ae1590e7fd83e133b3.png
每个人的年龄(Age)为15,姓名以J开头的人的国家(country)为"China",其他人为"HK,China"

接下来以几个案例验证CqlFormer的功能(见github中的example.py):

1.查询Amy的年龄:

myCF=CqlFormer()
print("How old is Amy:n{}".format(myCF.getSub(name="Amy").getReturn(sro=[],att=["s.Age"]).run()))

所得结果如下:

dae4f181aa9572b5ce904c2af20ac83a.png

1.查询认识Amy的人详细信息:

myCF=CqlFormer()
print("who knows Amy:n{}".format(myCF.getRel(["knows"]).getObj(name="Amy").getReturn().run()))

所得结果如下:

54c2743482472962740785704a7436ee.png

2.查询Amy认识的人有哪些:

myCF=CqlFormer()
print("Amyknows who:n{}".format(myCF.getSub(name="Amy").getRel(["knows"]).getReturn().run()))

所得结果如下:

14fa90906b80870b372c6aa6c440e752.png

3.查询认识Amy的有多少人(注意把sro调整为[]否则会依赖sro进行输出):

myCF=CqlFormer()
print("How many people know Amy:n{}".format(myCF.getRel(["knows"]).getObj(name="Amy").getReturn(sro=[],agg=[("count","s")]).run()))

所得结果如下:

4da66db1b51be0dd10f143544421c1d2.png

4.查询认识Amy的人的年龄:

myCF=CqlFormer()
print("what are the ages of people who know Alex:n{}".format(myCF.getRel(["knows"]).getObj(name="Alex").getReturn(sro=[],att=["s.name","s.Age"]).run()))

所得结果如下:

671f3a8e261497df3041a9fe730795d7.png

5.查询认识Amy的人的最大年龄:

myCF=CqlFormer()
print("what is the largest age of people who know Alex:n{}".format(myCF.getRel(["knows"]).getObj(name="Amy").getReturn(sro=[],agg=[("max","s.Age")]).run()))

所得结果如下:

6ddf5b2f92c818fb3d6f79ef9b90d843.png

6.查询认识Amy的人所处国家(区域)(注意att中若有和agg共现的属性,该语句将会变为统计语句,若没有共现则为罗列):

myCF=CqlFormer()
print("what is the countries/districts of people who know Amy:n{}".format(myCF.getRel(["knows"]).getObj(name="Amy").getReturn(sro=[],att=["s.country"]).run()))

所得结果如下:

101dd07c10b33407aeb9e3cc7d055c78.png

7.查询认识Amy的人都来自哪些国家:

myCF=CqlFormer()
print("what is the numbers of the countries/districts of people who know Amy:n{}".format(myCF.getRel(["knows"]).getObj(name="Amy").getReturn(sro=[],att=["s.country"],agg=[("count","s.country")]).run()))

所得结果如下:

0bbaf0a0d9553717025cbf0776b18ac4.png

10.注意:

  1. 每次构建了查询图后记得设定getReturn;
  2. 每次调用前需要通过CqlFormer进行一次初始化,否则容易出现数据紊乱;

今天就先更到这啦~回头发现问题再做补充,也欢迎大家多多交流~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值