[13] CQL
Neo4j 使用 CQL(Cypher Query Language)作为查询语言
1. 基本命令
Cypher 语言的关键字不区分大小写,但是属性值、标签、关系类型和变量是区分大小写的。
序号 | 关键字 | 关键字作用 |
---|---|---|
1 | CREATE | 创建 |
2 | MATCH | 匹配 |
3 | RETURN | 加载 |
4 | WHERE | 过滤检索条件 |
5 | DELETE | 删除节点和关系 |
6 | REMOVE | 删除节点和关系的属性 |
7 | ORDER BY | 排序 |
8 | SET | 添加或更新属性 |
9 | SKIP LIMIT | 分页 |
10 | DISTINCT | 去重 |
a. CREATE、MERGE
创建节点或者关系 (每个节点都有一个整数 ID),语法如下:
CREATE (<node-name>:<label-name>
[{
<property1-name>:<property1-Value>,
...
<propertyN-name>:<propertyN-Value>
}]
)
CREATE 举例:
// 创建多个标签
CREATE (p:Person:People)
// 创建无属性节点
CREATE (p:Person)
// 创建带属性节点
CREATE (p:Person{name:"刘备",word:"玄德",horse:"的卢",weapon:"双股剑",sex:"男"})
CREATE (p:Person{name:"关羽",word:"长生",horse:"赤兔",weapon:"青龙偃月刀"})
CREATE (p:Person{name:"张飞",word:"翼德",horse:"王追",weapon:"丈八蛇矛"})
CREATE (p:Person{name:"曹操",word:"孟德",horse:"绝影",weapon:"青虹剑"})
CREATE (p:Person{name:"孙权",word:"仲谋",horse:"快航",weapon:"白虹剑"})
// 使用现有节点创建没有属性的关系
MATCH (p:Person),(g:Person) WHERE p.name="刘备" AND g.name="关羽"
CREATE (p)-[r:MY_GENERAL]->(g)
// 使用现有节点创建有属性的关系
MATCH (p:Person),(g:Person)
WHERE p.name="刘备" AND g.name="孙权"
CREATE (p)-[r:MY_ENEMY{relative:"0"}]->(g)
RETURN r
// 一次创建两个节点一个子节点(Mask)和一个父节点(Old_mask),他们之间是属于父子关系
// 其中 create 表示新建,p 表示这个节点的别名,PERSON 表示〈节点 p〉的〈标签 PERSON〉的属性,`{}` 中间的键值对,表示〈节点 p〉作为〈标签 PERSON〉类别所拥有的属性,-[r:SON_OF]-> 表示〈节点 p〉指向 〈节点 f〉,他们之间的关系是 SON_OF,这个关系的别名是 r,r 可以拥有属于自己的属性,return 表示执行这段语句之后需要返回的对象,return p,r,f 表示返回〈节点 p〉和〈节点f〉,以及他们之间的〈关系 r〉。
CREATE (p:PERSON {name:"Mask",age:30,heigh:180,weight:80})-[r:SON_OF]->(f:PERSON {name:"OLD_Mask",age:55,heigh:160,weight:60})
RETURN p,r,f
MERGE 命令在图中搜索给定模式。如果存在,则返回结果;如果它不存在于图中,则它创建新的节点/关系并返回结果(MERGE = CREATE + MATCH)。
b. MATCH、RETURN
- MATCH 匹配命令用来查找指定节点、指定属性、指定关系的节点、关系,注意不能单独使用 MATCH 子句;
- RETURN 返回感兴趣的结果,不能单独使用 RETURN 子句; Neo4j 使用 CQL { MATCH + RETURN } 从数据库检索数据。
// 通过指定一个不带标签的节点的模式,图中的所有节点将返回
MATCH (n)
RETURN n
// 查询带有某个标签的所有节点(为查询的节点增加标签约束)
MATCH (movie:Movie)
RETURN movie.title
// 返回关联Person标签的名字叫'Mask'的节点所相连的其他节点以及关系。
MATCH (p:PERSON {name:"Mask"})-[r]-(n)
RETURN p,r,n
// 后面的别名 p 还可以利用 as 设置指定的返回值名称,如 `p AS userName`
MATCH (p:Person)
RETURN p.name AS userName
// 返回关联 Person 标签的名字叫 'Oliver' 的节点相连的带有 Movie标签的所有节点
MATCH (:Person { name: 'Oliver' })--(movie:Movie)
RETURN movie.title
c. DELETE、REMOVE
删除节点的操作也是通过 DELETE 来操作,如果被删除的节点存在 Relationship,那么单独删除该节点的操作会不成功,所以如果想删除一个已经存在关系的节点,需要同时将关系进行删除。与 MATCH 命令一起使用。 语法如下:DELETE <node-name-list>
// 删除单个节点
MATCH (n:Person {name:'UNKNOWN'}) DELETE n
// 删除所有节点和关系
MATCH (n) DETACH DELETE n
// 删除节点和它的关系
MATCH (n {name:'Andres'}) DETACH DELETE n
// 只删除关系
MATCH (n {name:'Andres'})-[r:'KNOWS']->() DELETE r
// 先通过匹配关键字 MATCH 找到匹配元素,然后通过 DELETE 关键字指定删除
MATCH (p: Person) WHERE p.name="刘备" DELETE p
MATCH (p:Person {name:"teacher_wange"}) DELETE p
// 删除节点和节点相关的关系
MATCH (p:Person {name:"lisi"})-[r]-() DELETE p,r
删除一个不存在的 Relationship 节点,会报错:
删除一个节点以及与它有关的关系,成功:
REMOVE 用来删除节点或关系的标签、属性,也是和 MATCH 命令一起使用。语法如下:
REMOVE <property-name-list>
。
// 删除节点P的“性别”属性
MATCH (p:Person)
REMOVE p.sex
RETURN p
// 删除节点的标签
MATCH (m:Person)
REMOVE m:People
d. SET、数据类型
向现有节点或关系添加或更新属性值,语法如下:SET <property-name-list>
。
// 给节点P添加字属性
MATCH (p:Person)
WHERE p.name="关羽"
SET p.word="云长"
RETURN p
CQL数据类型 | 作用 |
---|---|
boolean | 表示布尔文字:true, false |
byte | 表示 8 位整数 |
short | 表示 16 位整数 |
int | 表示 32 位整数 |
long | 表示 64 位整数 |
float | 表示 32 位浮点数 |
double | 表示 64 位浮点数 |
char | 表示 16 位字符 |
String | 表示字符串 |
e. ORDERBY、LIMIT、SKIP
- ORDER BY:与 SQL 里面是一样的操作,后面跟上需要根据排序的关键字,LIMIT 的操作是指定输出前几条。语法:
ORDER BY <property-name-list> [DESC]
; - LIMIT:过滤或限制查询返回的行数,语法:
LIMIT <number>
; - SKIP:从第几个记录开始,语法:
SKIP <number>
。
// 按照 p.name 按降序排序,LIMIT 表示只返回前 3 条数据
MATCH (p:Person)
RETURN p.name, p.word
ORDER BY p.name
LIMIT 3
// 返回从第 4 个起的 Person 标签的节点
MATCH (p:Person)
RETURN p
SKIP 4
f. WHERE、WITH
- WHERE: 基于某些标准过滤数据,在 WHERE 语句中使用 and、or、not、xor、= 、<>、<、>、<=、>= 等运算符;
- WITH:“管道” 功能,可以将一个查询的输出链接到另一个查询中,从而创建功能强大的图形结构,WITH 的每一个结果,必须使用别名标识。可以把 WITH 后面结果集当成一个查询结果、在这个结果基础上再做 WHERE 条件的筛选,比如下面这个语句:查询所有电影集、每个电影至少有 8 个以上的演员参演。
// WITH o, count(r) as count_r,以这个为结果集、然后在此基础上筛选出 count_r 大于 8 的
MATCH (n:Person)-[r:ACTED_IN]->(o:Movie)
WITH o, count(r) AS count_r
WHERE count_r > 8
RETURN o
MATCH (p:Person)-[:MY_GENERAL]->(g:Person)
WITH p, p.name AS name
WHERE name <> "刘备"
RETURN p.name, p.word,p.weapon
g. 操作符
Cypher 中的操作符有三个不同种类:数学,相等和关系
- 数学操作符有 +,-,*,/ 和 %。当然只有 + 对字符有作用;
- 等于操作符有 =,<>,<,>,<=,>=;
- 因为 Neo4j 是一个模式少的图形数据库,Cypher 有两个特殊的操作符
?
和!
。有些是用在属性上,有些事用于处理缺少值。对于一个不存在的属性做比较会导致错误。为替代与其他什么做比较时总是检查属性是否存在,在缺失属性时?
将使得比较总是返回 true,!
使得比较总是返回 false(警告:在同一个比较中混合使用两个符号将导致不可预料的结果)。
WHERE n.prop ?= “foo” # 这个断言在属性缺失情况下将评估为 true
WHERE n.prop != “foo” # 这个断言在属性缺失情况下将评估为 false
2. Cypher 函数
2.1 字符串函数
函数 | 功能 |
---|---|
UPPER | 将所有字母改为大写字母 |
LOWER | 将所有字母改为小写字母 |
SUBSTRING | 获取给定String的子字符串 |
REPLACE | 替换一个字符串的子字符串 |
2.2 聚合函数
聚合函数用于对查询的结果进行统计:
- avg():计算均值
- count(exp):用于计算非 null 值(value)的数量,使用 count(distinct exp) 进行无重复非 null 值的计数,使用 count(*) 计算值或记录的总数量,包括 null 值
- max()/min():求最大值和最小值,在计算极值时,null 被排除在外,min(null) 或 max(null) 返回 null
- sum():求和,在求和时,null 被排除在外,sum(null) 的结果是 0
- collect():把返回的多个值或记录组装成一个列表,collect(null) 返回一个空的列表
在聚合计算中,可以引用分组键来对查询的结果进行分组聚合,例如,在 return 子句中,如果表达式不是聚合函数,那么该表达式是分组 key,下面的表达式是按照 type(r) 分组,计算每个分组中记录的数量。
RETURN type(r), count(*)
举例:
MATCH(p:Person)
RETURN p.name AS name,p.age AS age,count(p) AS count,max(p.age) AS maxAge,
min(p.age) AS minAge,avg(p.age) AS avgAge,sum(p.age) AS sumAge
2.3 标量函数
a. 获得ID和属性
- id():返回节点或关系的 ID(内部编号)
- properties():返回节点或关系的属性(Map)
CREATE (p:Person { name: 'Stefan', city: 'Berlin' })
RETURN id(p), properties(p)
b. 关系
函数 | 功能 |
---|---|
STARTNODE | 用于知道关系的开始节点 |
ENDNODE | 用于知道关系的结束节点 |
TYPE | 查找一个关系的类型 |
HAS | 如果一个节点或关系具有给定名字的属性存在,返回true |
NODES | 把一个路径转换成一个可迭代的节点集 |
先获取关系,然后通过关系函数来获取关系的id、type、起始节点、终止节点等等信息
MATCH ()-[r:SON_OF]-()
RETURN
STARTNODE(r).name AS start_node,
ENDNODE(r).name AS end_node,
ID(r) AS relationship_id,
TYPE(r) AS realtionship_type
c. 长度/数量
- size(string):表示字符串中字符的数量,可以把字符串当作是字符的列表;
- size(list):返回列表中元素的数量;
- size(pattern_expression):也是统计列表中元素的数量,但是不是直接传入列表,而是提供模式表达式(pattern_expression),用于在匹配查询(Match query)中提供一组新的结果,这些结果是路径列表,size 函数用于统计路径列表中元素(即路径)的数量;
- length(path):返回路径的长度,即路径中关系的数量。
MATCH (a)
WHERE a.name = 'Alice'
RETURN size( (a)-->()-->() ) AS fof
2.4 谓词函数
谓词函数返回 true 或者 false,主要用于检查是否存在或满足特定的条件。
a. 存在
exist 函数:如果指定的模式存在于图中,或者特定的属性存在于节点、关系或 Map 中,那么函数返回 true
// 例如,节点具有 name 属性,并 check 图中是否存在特定的模式
MATCH (n)
WHERE exists(n.name)
RETURN n.name AS name, exists((n)-[:MARRIED]->()) AS is_married
b. 检查集合元素
all()
表示所有的元素都满足条件,any()
表示至少一个元素满足条件,none()
函数表示没有一个元素满足条件,single()
表示只有一个元素满足条件。
-
ALL 谓词表示,在路径中,所有节点都必须具有 age 属性,并且 age 属性值都必须大于 30:
MATCH p =(a)-[*1..3]->(b) WHERE a.name = 'Alice' AND b.name = 'Daniel' AND ALL (x IN nodes(p) WHERE x.age > 30) RETURN p
-
ANY 谓词表示,节点的 array 属性中至少有一个元素值是 one:
MATCH (a) WHERE a.name = 'Eskil' AND ANY (x IN a.array WHERE x = 'one') RETURN a.name, a.array
-
NONE 谓词表示,在路径中,没有节点的 age 属性等于 25
MATCH p =(n)-[*1..3]->(b) WHERE n.name = 'Alice' AND NONE (x IN nodes(p) WHERE x.age = 25) RETURN p
-
SINGLE 谓词表示,在路径中,只有一个节点的 eyes 属性是 blue:
MATCH p =(n)-->(b) WHERE n.name = 'Alice' AND SINGLE (var IN nodes(p) WHERE var.eyes = 'blue') RETURN p
2.5 列表函数
列表是 Cypher 中的一个重要的复合类型,对列表进行操作的函数主要是生成列表、获取列表对象、抽取特定的列表元素、过滤列表元素和对列表元素进行迭代计算。
a. 抽取元素构成列表
extract 函数的作用是从列表中抽取值,构成列表。语法如下:extract(variable IN list | expression)
// 根据抽取的值组装成一个列表,返回一个列表
MATCH p =(a)-->(b)-->(c)
WHERE a.name = 'Alice' AND b.name = 'Bob' AND c.name = 'Daniel'
RETURN extract(n IN nodes(p)| n.age) AS extracted
b. 过滤列表元素
filter 函数用于对列表中的元素进行过滤,语法如下:filter(variable IN list WHERE predicate)
// 把过滤后的元素组成一个了表,返回该列表
MATCH (a)
WHERE a.name = 'Eskil'
RETURN a.array, filter(x IN a.array WHERE size(x)= 3)
c. 获取列表
- keys(node):从节点的属性中抽取属性键
- labels(node):节点标签的列表
- nodes(path):从路径中获取所有节点的列表
- relationships(path):从路径中获得所有的关系
- coalesce():返回列表中第一个非NULL的元素
- head():返回列表中的第一个元素
- last():返回列表中的最后一个元素
- size():返回列表中元素的数量
MATCH (a)
WHERE a.name = 'Alice'
RETURN labels(a),keys(a)
MATCH p =(a)-->(b)-->(c)
WHERE a.name = 'Alice' AND c.name = 'Eskil'
RETURN nodes(p), relationships(p)
d. 序列的生成和倒置
range 函数用于生成一个有序的序列,reverse 函数把原始列表的元素进行倒置
// 将列表中下标索引为 3 的元素也就是第 4 个元素返回
RETURN range(0, 10) as list, range(0, 10)[3] as result
// 与Python不同的是这里的range是左闭右闭的,即一个列表中有11个元素
range(0, 10)[-3] //倒数第三个元素
reverse(list)
e. 迭代计算列表
reduce 函数应用在列表上,对列表中的每个元素 e 进行迭代计算,在元素 e 上运行表达式(expression),把当前的结果存储在累加器中,进行迭代计算,并返回最终计算的标量结果。语法:reduce(accumulator = initial, e IN list | expression)
// 初始的age值是0,对路径p中的所有节点,计算各个节点的age值的和
MATCH p =(a)-->(b)-->(c)
WHERE a.name = 'Alice' AND b.name = 'Bob' AND c.name = 'Daniel'
RETURN reduce(totalAge = 0, n IN nodes(p)| totalAge + n.age) AS reduction
3. 模式
模式和模式匹配是 Cypher 的核心,使用模式来描述所需数据的形状,该模式使用属性图的结构来描述,通常使用小括号 ()
表示节点,--> / <--
表示关系,-[]->
表示关系和关系的类型,箭头表示关系的方向。
3.1 节点模式
用小括号表示节点模式:(a)
,a 是节点变量的名称,用于引用图中的某一个节点 a。
对于匿名的节点,可以使用 ()
来表示,匿名的节点无法引用,通常用来表示路径中的占位节点。
a. 标签模式
在节点变量的后面,使用 :Lable
来表示标签,标签是节点的分组,一个节点可以有一个标签,也可以有多个标签,比如,(a:User)
,(a:User:Admin)
。
b. 指定属性
节点和关系都有属性,属性模式可以使用 Map 结构来表示,属性模式的格式是 { key:value,..}
,使用大括号表示一个字典,包含一个或多个键/值对:
(a {name: 'Andres', sport: 'Brazilian Ju-Jitsu'})
(a)-[{blocked: false}]->(b)
3.2 关系模式
关系模式是由节点和路径来描述的,最简单的关系模式两个节点和一个路径:
(a)--(b)
该模式表示节点 a 和节点 b 之间存在关系,不指定关系的方向。
a. 关系的名称和方向
当模式包含一簇关系时,关系模式不会指定方向,Cypher 将尝试匹配两个方向的关系:
// 关系也可以被命名,Cypher 使用 [r] 来表示关系变量
(a)-[r]-(b)
若指定关系是有方向的,使用箭头指定关系的方向:
(a)-[r]->(b)
注意:图中关系的方向是在创建关系时指定的,在执行 Cypher 查询时,如果指定关系的方向,那么沿着关系的方向进行模式匹配。
b. 关系类型
就像节点具有标签,可以对节点进行分组,关系也可以分组,Neo4j 按照关系的类型对关系进行分组:
// 当已知要匹配关系的类型时,可通过冒号后面紧跟关系类型
(a)-[r:REL_TYPE]->(b)
但是不像节点可以有多个标签,关系只能由一个关系类型,但是,关系的类型可以属于一个集合,这使用管道符号 | 来分割,表示关系输入集合中的任意一个类型:
(a)-[r:TYPE1|TYPE2]->(b)
// 返回与'The Matrix'节点关系为ACTED_IN或者DIRECTED的所有节点
MATCH (wallstreet { title: 'The Matrix' })<-[:ACTED_IN|:DIRECTED]-(person)
RETURN person.name
3.3 路径模式
路径是由节点和关系构成的序列,在路径中节点和关系是交替相连的,不可中断。路径的长度是指关系的数量,固定长度的路径是指:路径中关系的数量是固定不变的,可变长度的路径是:指路径中关系的数量是可变的。关系的数量有两种表示方式:固定长度和变长。
- 固定长度的关系,使用
[* n]
来表示 - 变长的关系,使用
[*start..end]
来表示,其中..
表示关系的长度是可变的,start 表示关系数量的最小值,end 表示关系数量的最大值。
【注】
- start 和 end 都可以省略,如果省略 start,那么关系的长度
<= end
;如果省略 end,那么关系的长度>= start
;如果同时省略 start 和 end,那么关系的长度是任意的; - 在变长关系模式中,也可以指定关系的类型:
[Type * start .. end ]
,变长关系只能用于 MATCH 查询语句中,不能用于 CREATE 和 MERGE 语句中。
a. 固定长度的关系
在关系 []
中,使用 *2
表示关系的长度为 2,使用该模式来表示路径,路径两端的节点是 a 和 b,路径中间的节点是匿名的,无法通过变量来引用。
(a)-[*2]->(b)
该模式描述了 3 个节点和 2 个关系,路径两端的节点是 a 和 b,中间节点是匿名节点,等价于以下的模式:
(a)-->()-->(b)
b. 变长关系
在关系 []
中,使用 [*start .. end]
来表示变长关系
(a)-[*3..5]->(b)
(a)-[*3..]->(b)
(a)-[*..5]->(b)
(a)-[*]->(b)
c. 路径变量
Cypher 允许对 Path 命名,把 Path 赋值给变量 p,路径模式可以使用 p 来表示:
p = (a)-[*3..5]->(b)
有如下的有向图数据,按照有向图来计算路径,最长的路径长度是2;按照无向图来计算路径,最长的路径长度是6。
分析以下 Cypher 查询,在路径模式中,路径是无向的(--
表示不考虑关系的方向,所以也会反向查找找出走 1 个关系或者 2 个关系的节点),路径的长度是 1 或 2,关系的类型是 KNOWS,节点 Filipa 和节点 remote_friend 在同一条路径中:
MATCH (me)-[:KNOWS*1..2]-(remote_friend)
WHERE me.name = 'Filipa'
RETURN remote_friend.name
4. 索引
Neo4j CQL 支持节点或关系属性上的索引,以提高应用程序的性能。
我们可以为具有相同标签名称的所有节点的属性创建索引,继而在 MATCH / WHERE / IN 运算符上使用这些索引列来改进 CQL Command 的执行。
4.1 创建索引
创建单一索引:
// CREATE INDEX ON :<label_name> (<property_name>)
CREATE INDEX ON :Person(name)
创建复合索引:
CREATE INDEX ON :Person(age, gender)
4.2 全文索引
之前的常规模式索引只能对字符串进行精确匹配或者前后缀索引(startswith,endswith,contains),全文索引将标记化索引字符串值,因此它可以匹配字符串中任何位置的术语。索引字符串如何被标记化并分解为术语,取决于配置全文模式索引的分析器。索引是通过属性来创建,便于快速查找节点或者关系。
使用 db.index.fulltext.createNodeIndex
和 db.index.fulltext.createRelationshipIndex
创建全文模式索引。在创建索引时,每个索引必须为每个索引指定一个唯一的名称,用于在查询或删除索引时引用相关的特定索引。然后,全文模式索引分别应用于标签列表或关系类型列表,分别用于节点和关系索引,然后应用于属性名称列表。
call db.index.fulltext.createNodeIndex("索引名",[Label,Label],[属性,属性])
call db.index.fulltext.createNodeIndex("nameAndDescription",["Person"],["name", "description"])
call db.index.fulltext.queryNodes("nameAndDescription", "刘佳琦") YIELD node, score RETURN node.name, node.description, score
4.3 删除/查询
// --- 查询
call db.indexes
:schema
call db.index.fulltext.drop("nameAndDescription")
// --- 删除
DROP INDEX ON :Person(name)
DROP INDEX ON :Person(age, gender)
5. 约束
在 Neo4j 数据库中,CQL CREATE 命令始终创建新的节点或关系,这意味着即使您使用相同的值,它也会插入一个新行。 根据我们对某些节点或关系的应用需求,我们必须避免这种重复。 然后我们不能直接得到这个。 我们应该使用一些数据库约束来创建节点或关系的一个或多个属性的规则。
像 SQL 一样,Neo4j 数据库也支持对 NODE 或 Relationship 的属性的 UNIQUE 约束,UNIQUE 约束的使用是为了避免重复记录,强制执行数据完整性规则。语法如下:
CREATE CONSTRAINT ON (<label_name>) ASSERT <property_name> IS UNIQUE
Neo4j CQL 提供了“DROP CONSTRAINT”命令,以从 NODE 或 Relationship 的属性中删除现有的 Unique 约束。语法如下:
DROP CONSTRAINT ON (<label_name>) ASSERT <property_name> IS UNIQUE
查看约束:
call db.constraints
:schema