目录
Cypher手册详解
最近在研究知识图谱,避免不了的涉及到了图数据库和图算法,我们用的图数据库是neo4j,对其CQL语法做一个记录。整篇文章是对官网Cypher手册的翻译(正常访问超级慢,也许要翻墙),同时做了一些删减,例如地理函数和时间函数,和我的项目关系不大,就没有学习,对于官网的一些示例加上了结果的截图和自己的理解,本博文基于Neo4j3.5.3,其余版本可能会报错。如有错误,还麻烦各位大佬指正,非常感谢。
1 背景
什么是neo4j?
Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中。它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎,但是它将结构化数据存储在网络(从数学角度叫做图)上而不是表中。Neo4j也可以被看作是一个高性能的图引擎,该引擎具有成熟数据库的所有特性。程序员工作在一个面向对象的、灵活的网络结构下而不是严格、静态的表中--但是他们可以享受到具备完全的事务特性、企业级的数据库的所有好处。
neo4j的呈现形式:Neo4j中不存在表的概念,只有两类:节点(Node)和关系(Relation),可以简单理解为图里面的点和边。
在数据查询中,节点一般用小括号(),关系用中括号[]。
当然也隐含路径的概念,是用节点和关联表示的,如:(a)-[r]->(b),表示一条从节点a经关联r到节点b的路径。
关系:neo4j中是单向关系,严格的来说不具备双向或者无向的关系。但是merge (a)-[r]-(b)这样的语句创建的关系可以理解为是双向的,但是neo4j中比较尴尬的一点是这样可以理解为无向关系的关系,在web呈现时是带着单向箭头的。
属性:节点和关系都可以具备属性。
标签:代表节点的类型,一个节点可以有0个、1个或者多个标签。
类型:代表关系的类型,一条关系可以有0个或者1个,一条边不能具有多个type。
2 唯一性
在模式匹配时,neo4j确保不会在单个模式中多次找到相同图形关系的匹配。举例,在寻找朋友的朋友时,不会返回所述用户自身。
CREATE (adam:User { name: 'Adam' }),(pernilla:User { name: 'Pernilla' }),(david:User { name: 'David'}),(adam)-[:FRIEND]->(pernilla),(pernilla)-[:FRIEND]->(david)
寻找Adam朋友的朋友
MATCH (user:User { name: 'Adam' })-[r1:FRIEND]-()-[r2:FRIEND]-(friend_of_a_friend)
RETURN friend_of_a_friend.name AS fofName
返回David
因为r1和r2在同一个模式中,又是不同的变量名,所以不会返回同一条关系,即图形中的有向边。检验:在两个子句中用不同的变量名就不管用
MATCH (user:User { name: 'Adam' })-[r1:FRIEND]-(friend)
MATCH (friend)-[r2:FRIEND]-(friend_of_a_friend)
RETURN friend_of_a_friend.name AS fofName
但是只要在一个模式中,即使拆分了多个子模式也不会匹配到同一关系,如下所示。
MATCH (user:User { name: 'Adam' })-[r1:FRIEND]-(friend),(friend)-[r2:FRIEND]-(friend_of_a_friend)
RETURN friend_of_a_friend.name AS fofName
3 语法
3.1 命名规则
必须以字母开头,不能以数字开头。
节点标签:驼峰式命名,关系类型:全大写命名。
3.2 表达式
十进制、十六进制:0x13ff,0xFCA39
八进制整数文字:01372、02127
字符串、bool类型
属性:n.prop、x.prop、rel.thisProperty
动态属性:n["prop"], rel[n.city + n.zip], map[coll[0]]
参数:$param, $0
表达式列表:['a', 'b'], [1, 2, 3], ['a', 2, n.property, $param], [ ].
函数调用:length(p) nodes(p)
聚合函数,路径模式
正则表达式:a.name =~ 'Tim.*'
字符串匹配:
surname STARTS WITH 'Sven',
surname ENDS WITH 'son' or a.surname CONTAINS 'son'
CASE表达式
3.2.1 字符串常用的转义序列
\t Tab键 \b 后退键 \n 新起一行 \r 回车符 \f 制表符 \’ 单引号 \’’双引号
\\ 反斜杠的转义
3.2.2 CASE表达式
计算表达式,并按顺序与WHEN子句进行比较,直到找到匹配项。 如果未找到匹配项,则返回ELSE子句中的表达式。 但是,如果没有ELSE情况且未找到匹配项,则返回null。
CASE test
WHEN value THEN result
[WHEN ...]
[ELSE default]
END
建表语句:
create (A:Person {name:'Alice', eyes:"brown",age:38}),
(B:Person {name:"Bob", eyes:"blue", age:25}),
(C:Person {name:"Charlie", eyes:'green',age:53}),
(D:Person {name:"Daniel", eyes:'brown'}),
(E:Person {name:'Eskil',eyes:"blue",age:41,array:['one','two','three']}),
(A)-[:KNOWS]->(B),
(A)-[:KNOWS]->(C),
(B)-[:KNOWS]->(D),
(C)-[:KNOWS]->(D),
(B)-[:MARRIED]->(E)
测试语句1:
MATCH (n:Person)
RETURN n.name,
CASE n.eyes
WHEN 'blue'
THEN 1
WHEN 'brown'
THEN 2
ELSE 3 END AS result
测试语句2:
MATCH (n:Person)
RETURN n.name,
CASE
WHEN n.eyes = 'blue'
THEN 1
WHEN n.age < 40
THEN 2
ELSE 3 END AS result
测试语句3:
我们看到建表语句中,存在一些节点时没有age属性的,我们希望返回一个age_10_years_ago的值,假设不存在age属性,将该值返回-1。我们按照如下的方式来试写查询语句,期望对于Daniel返回-1,因为他不具备age属性。但是并不如意:
MATCH (n:Person)
RETURN n.name,
CASE n.age
WHEN n.age IS NULL THEN -1
ELSE n.age - 10 END AS age_10_years_ago
原因:n.age是一个整型,而n.age IS NULL是一个bool值,所以不会走到WHEN n.age IS NULL THEN -1这个对应的分支,需要换成如下的写法:
MATCH (n:Person)
RETURN n.name,
CASE
WHEN n.age IS NULL THEN -1
ELSE n.age - 10 END AS age_10_years_ago
换一种方式理解,就是常用的switch(n.age), 不会等于 n.age is null这个case ,想要返回-1直接写成如下方式即可。
MATCH (n:Person)
RETURN n.name,
CASE n.age
WHEN NULL THEN -1
ELSE n.age - 10 END AS age_10_years_ago
3.3 变量与保留关键字
变量仅在同一查询部分中可见
变量不会转移到后续查询中。 如果使用WITH将多个查询部分链接在一起,则必须在WITH子句中列出变量以将其转移到下一部分。
保留关键字不能用于变量名、函数名、参数。
3.4 参数
Cypher支持使用参数查询。这意味着开发人员不必使用字符串构建来创建查询。此外,参数使得Cypher的执行计划缓存更加容易,从而缩短了查询执行时间。
参数可用于:文字和表达式
节点和关系id
仅用于显式索引:索引值和查询
参数不能用于以下构造,因为它们构成了编译到查询计划中的查询结构的一部分:
属性键;所以,MATCH(n)WHERE n.$ param ='something'无效
关系类型
标签
参数可以包含字母和数字,以及这些参数的任意组合,但不能以数字或货币符号开头。
3.4.1 参数定义
参数仅仅对当前会话有效,网页刷新变量消失。变量为一个kv的键值对。
:param a:1, b:2或者:param {a: 1, b: 2}以这样的形式来定义参数,注意前面是有冒号的。
MATCH (n:Person)
WHERE n.name = $name
RETURN n
MATCH (n:Person { name: $name })
RETURN n
查看当前的所有参数:
:params
3.4.2 可以使用参数的各种场景
正则表达式:
:param {"regex":".*VM.*"}
match (n:VM) where n.name=~ $regex return n.name
大小写敏感的字符串匹配
:params { "name" : "Michael"}
MATCH (n:Person)
WHERE n.name STARTS WITH $name
RETURN n.name
创建多个带属性带标签的节点
:param {"props" : [ {
"awesome" : true,
"name" : "Andy",
"position" : "Developer"
}, {
"children" : 3,
"name" : "Michael",
"position" : "Developer"
} ]}
UNWIND $props AS properties
CREATE (n:Person)
SET n = properties
RETURN n
判断某个变量在或者不在变量列表中
:param "ids" : [ 0, 1, 2 ]
//判断变量在列表中,如何判断变量不在对应的列表中?not in会报错,<>达不到想要的结果
MATCH (n)
WHERE id(n) IN $ids
RETURN n.name
//判断变量不存在于列表中
match(n:VM) where size([l in [id(n)] where l in $ids ])=0 return n
调用函数
:param "value" : "Michaela"
START n=node:people(name = $value)
RETURN n
3.5 操作符
3.5.1 使用[]
来访问动态计算的属性键 对多个属性键来进行共同筛选
CREATE (a:Restaurant { name: 'Hungry Jo', rating_hygiene: 10, rating_food: 7 }),(b:Restaurant { name: 'Buttercup Tea Rooms', rating_hygiene: 5, rating_food: 6 }),(c1:Category { name: 'hygiene' }),(c2:Category { name: 'food' })
WITH a, b, c1, c2
MATCH (restaurant:Restaurant),(category:Category)
WHERE restaurant["rating_" + category.name]> 6
RETURN DISTINCT restaurant.name
3.5.2 ^完成指数运算
3.5.3字符串判断
STARTS WITH ENDS WITH CONTAINS
3.5.4 使用in进行更加复杂的列表成员操作
RETURN [2, 1] IN [1,[2, 1], 3] AS inList 会return true。
RETURN [1, 2] IN [1, 2] AS inList 会return false 这里的in不是判断认为两个是整体,而是将左边作为一个整体元素,判断左边是否为右边的子元素
如下查询可以用于判断llhs是否至少包含一个也存在于lrhs中的元素
MATCH (n)//判断当前库中的所有节点,有哪些的标签是Person或者Employee
WHERE size([l IN labels(n) WHERE l IN ['Person', 'Employee'] | 1]) > 0
RETURN count(n)
3.5.5 用[]获取列表元素
WITH ['Anne', 'John', 'Bill', 'Diane', 'Eve'] AS names
RETURN names[1..3] AS result
这里的方括号是左闭右开的,
所以会返回[‘John’,‘Bill’]
和C语言一样//表示对应的注释
3.6 模式
模式和模式匹配时Cypher的核心,有效使用Cypher需要对模式有一个正确的理解。
最为简单的模式就是一个双括号括上一个变量名,例如(a) a就是一个变量名
(a)-->(b)
(a)-->(b)<--(c)这样的一系列的节点和关系称之为路径
可以在模式中描述的最简单的属性称之为标签(a:User)-->(b) 同时可以描述具备多个标签的节点(a:User:Admin)-->(b)
3.6.1 指定属性
对于MERGE子句,属性将用作任何现有数据必须具有的形状的附加约束(指定的属性必须与图中的任何现有数据完全匹配)。 如果未找到匹配的数据,则MERGE的行为类似于CREATE,并且将在新创建的节点和关系中设置属性。
3.6.2 关系的模式
类似于节点的标签,对于关系来说叫做类型,但是和节点标签不同的是,关系的类型仅仅只有一种。如果我们想要描述一些数据,使得这种关系可以有一组类型中的任何一种,那么它们都可以在模式中列出,用管道符号将它们分开。像这样(但是仅仅适用于match,不适用于Create和merge):
(a)-[r:TYPE1|TYPE2]->(b)
关系名称通常也可以省略
3.6.3 变量长度的模式匹配
在模式的关系描述中指定长度来进行对应的描述
(a)-[*2]->(b) 等价于 (a)-->()-->(b)
(a)-[*3..5]->(b)这一关系是左闭右闭,即包含3个关系、4个关系、5个关系
(a)-[*3..]->(b) (a)-[*..5]->(b)
需要特别注意的一点是,即使关系是单向的,在如下的语句中,--表示不考虑关系的方向,仍然会反向查找找出走一个关系或者两个关系的节点。
MATCH (me)-[:KNOWS*1..2]-(remote_friend)
WHERE me.name = 'Filipa'
RETURN remote_friend.name
3.7 列表
RETURN range(0, 10) as list ,range(0, 10)[3] as result//将列表中下标索引为3的元素也就是第4个元素返回
与Python不同的是 这里的range是左闭右闭的,即一个列表中有11个元素
range(0, 10)[-3]//倒数第三个元素
RETURN range(0, 10)[0..3]//左闭右开
RETURN range(0, 10)[-5..]或者[..4]//只有在list中的a..b是左闭右开的,路径长度的匹配与range范围中的a..b都左闭右闭的。
//也可以理解为返回倒数5个元素的列表和正数4个元素的列表
RETURN size(range(0, 10)[0..3])//size函数的返回结果是3
3.7.1 列表推导
RETURN [x IN range(0,10) WHERE x % 2 = 0 | x^3] AS result//这里的竖线不是或,而是管道
MATCH (a:Person { name: 'Charlie Sheen' })
RETURN [(a)-->(b) WHERE b:Movie | b.year] AS years
3.8 映射
Cypher坚定地支持映射,对map的处理不太理解。
3.9 使用null
Cypher中的null表示缺失或未定义的值,null并不等于null,不知道的两个值并不意味着它们相等 ,所以null=null会抛出null而不是true。
null IN [1, 2, 3]
null IN [1, null, 3]
null in []
如果使用null来做列表切片的首尾索引值,那么返回值也是null。