我们在使用 Neo4j 图数据库时,经常会接触到结点、关系、属性、标签等概念。结点是描述对象的实体,类似于面向对象中一个实例化出来的对象。对象内部的实际数据我们用属性来描述。
我们都知道对象是有类型的,类型可以用标签来描述,在 Neo4j 图数据库中,一个结点对象 C 可以有多个标签,即多个类型,即:我们可以定义一个对象既属于 A 类型,也属于 B 类型 ... 当对象有多个标签时,我们更倾向于把标签理解为分类。比如在查询时,可以查询某个分类下的所有结点。当查询 A 类或者 B 类下所有结点时,结点 C 都会出现。
假设我们有两个结点 C、D,这两个结点之间存在某种关系,例如:C 是 D 的男朋友,D 是 C 的女朋友,这个结点间的信息就可以用关系来描述。注意,关系是有方向的。也需要注意,有时,我们为了更进一步描述关系,可以给关系添加一些属性。
标签一般用大驼峰法表示,关系一般用全大写表示,属性一般用条驼峰法表示。
# Mac 安装 Neo4j
brew install neo4j
# 启动 Neo4j 服务
neo4j start
# 关闭 Neo4j 服务
neo4j stop
# 重启 Neo4j 服务
neo4j restart
Cypher 是 Neo4j 图数据库中的 SQL 语句,可以用来方便的创建结点、关系,以及根据条件进行查询相关信息。
- 结点操作 {#title-0} ==================
结点操作主要有 CREATE、DELETE、MERGE 等关键字。我们先使用 CREATE 关键字来创建几个用于演示的结点:
create (:Test{name: 'aaa'})
create (:Test{name: 'bbb'})
create (:Test{name: 'ccc'})
# 查看添加的结点
match (n:Test) return n.name
接下来,使用 DELETE 来删除 name = 'bbb' 和 name = 'ccc' 的结点:
# 删除 name = 'bbb' 的结点
match (n:Test{name: 'bbb'}) delete n
# 删除 name = 'ccc' 的结点
match (n:Test) where n.name = 'ccc' delete n
# 删除所有 Test 类型的结点
match (n:Test) delete n
再回到创建结点的操作,有时我们想查询某个结点,当该结点不存在时,我们再新建结点。此时,可以使用 MERGE 关键字。MERGE 关键字不单单对于结点操作,也可以用于对结点关系的操作。
merge (n:Test{name: 'aaa'})
on create set n.age = 100
return n.name, n.age
- 属性操作 {#title-1} ==================
属性操作可以使用 SET、REMOVE 关键字,注意结点和关系都有属性,所以这些关键字对属性的操作适用于关系操作和结点操作。
我们先使用 CREATE 新增几个结点:
create (:Person{name: '张三', age: 18})
create (:Person{name: '赵六', age: 19})
SET 关键字可用于新增、修改结点属性值。我们先把赵六的年龄修改为 20,把张三的年龄修改为 15。可以使用下面两种方法:
# 赵六的年龄修改为 20
match (n:Person{name: '赵六'}) set n.age = 20 return n.name, n.age
# 张三的年龄修改为 15
match (n:Person) where n.name = '张三' set n.age = 15 return n.name, n.age
新增结点属性也较为容器,直接查询到相关的结点,set 属性即可。例如:我们要给赵六增加一个性别的属性:
match (p:Person{name:'赵六'}) set p.sex = '男' return p.name, p.age, p.sex
2.2 REMOVE 属性 {#title-2}
删除结点使用 DELETE,删除结点属性使用 REMOVE。
match (n:Test{name: 'aaa'}) remove n.age return n.name, n.age
- 关系操作 {#title-3} ==================
创建关系时,主要分为给已存在结点创建关系,还是在创建新结点时创建关系。我们先看后者,创建两个结点,并指定两个结点为 FRIEND 关系。注意:关系是具有方向性的。
我们先创建两个 Student 结点,并指定 Obama 是 Trump 的朋友,这并不代表 Trump 是 Obama 的朋友。
create (a:Student{name: 'Obama'})-[r:FRIEND]->(b:Student{name: 'Trump'}) return a, b
我们现在指定 Trump 也是 Obama 的朋友,此时两个结点已经存在。基本思路是,先 match 两个结点,然后使用 merge 来创建结点之间的关系,merge 的优点是如果关系存在则不创建,否则则创建关系。
match (a:Student{name:'Obama'}), (b:Student{name:'Trump'}) merge (b)-[r:FRIEND]->(a) return a, b
假设,我们要删除 Obama 对 Trump 的朋友关系,这里要使用 DELETE 关键字。删除结点和关系必须使用该关键字。先查询下,Obama 结点对 Trump 结点的关系:
match (a:Student{name:'Obama'})-[r]->(b:Student{name:'Trump'})
return a.name, b.name, type(r)
接下来,使用 DELETE 删除这层关系:
match (a:Student{name:'Obama'})-[r]->(b:Student{name:'Trump'}) delete r
# 查看两个结点之间的关系
match (a:Student{name:'Obama'}), (b:Student{name:'Trump'}) return a, b
我们会发现双向的 FRIEND 关系,现在只剩下单向的 FRIEND 关系了。
- 查询操作 {#title-4} ==================
先把演示数据库中所有的结点删除,我们构建一批新的结点,以及结点之间的关系。
match (n) detach delete n
4.1 构建结点关系 {#title-5}
创建 Person 类型 5 个结点,Country 类型 2 个结点,如下所示:
# 插入 Peron 类型的 5 个结点
create (:Person{name: '张三', age: 18})
create (:Person{name: '李四', age: 18})
create (:Person{name: '王五', age: 16})
create (:Person{name: '赵六', age: 19})
create (:Person{name: '邓七', age: 17})
# 插入 Country 类型的 2 个结点
create (:Country{name: 'china'})
create (:Country{name: 'american'})
接下来,查看下所有结点的信息:
# 查询国家结点信息
match (n:Country) return n.name as 国家
# 根据年龄降序查看所有 Person 信息
match (n:Person) return n.name as 姓名, n.age as 年龄 order by 年龄 desc
我们开始构建结点之间的关系:
- 张三和李四是王五的朋友
- 王五是赵六、邓七的朋友
- 李四和邓七生活在中国
- 张三、王五、赵六生活在美国
- 美国是中国的附庸国
- 张三、王五、赵六增加新的 VIP 标签
我们接下来,使用 cypher 来构建出这些结点的关系
# 张三和李四是王五的朋友
match (p1:Person{name:'张三'}),(p2:Person{name:'李四'}),(p3:Person{name:'王五'})
merge (p1)-[:FRIEND]->(p3)
merge (p2)-[:FRIEND]->(p3)
return p1, p2, p3
执行结果如下图所示:
# 王五是赵六、邓七的朋友
match (p1:Person{name:'王五'}),(p2:Person{name:'赵六'}),(p3:Person{name:'邓七'})
merge (p1)-[:FRIEND]->(p2)
merge (p1)-[:FRIEND]->(p3)
return p1, p2, p3
执行结果如下图所示:
# 李四和邓七生活在中国
match (p1:Person{name:'李四'}),(p2:Person{name:'邓七'}),(c:Country{name:'china'})
merge (p1)-[:LIVE_IN]->(c)
merge (p2)-[:LIVE_IN]->(c)
return p1, p2, c
执行结果如下图所示:
# 张三、王五、赵六生活在美国
match (p1:Person{name:'张三'}),(p2:Person{name:'王五'}),(p3:Person{name:'赵六'}),(c:Country{name:'american'})
merge (p1)-[:LIVE_IN]->(c)
merge (p2)-[:LIVE_IN]->(c)
merge (p3)-[:LIVE_IN]->(c)
return p1, p2, p3, c
执行结果如下图所示:
# 美国是中国的附庸国
match (c1:Country{name:'china'}), (c2:Country{name:'american'})
merge (c2)-[:BELONG_TO]->(c1)
return c1, c2
执行结果如下图所示:
# 张三、王五、赵六增加新的 VIP 标签
match (p1:Person{name:'张三'}),(p2:Person{name:'王五'}),(p3:Person{name:'赵六'}) set p1:VIP, p2:VIP, p3:VIP return p1, p2, p3
# 查询 VIP 分类下所有的结点
match (n:VIP) return n.name, n.age, labels(n)
到目前为止,我们已经构建了一个较为复杂的对象关系网。查看所有结点之间的关系:
match (n) return n
执行结果如下图所示:
4.2 查询结点数据 {#title-6}
# 1. 查询所有的 Person 类型结点
match (p:Person) return p.name as 姓名, p.age as 年龄
# 2. 查询所有的 Country 类型的结点
match (c:Country) return c.name as 国家
# 3. 查询所有王五的朋友,注意: 王五指向的朋友
match (:Person{name:'王五'})-[:FRIEND]->(p:Person) return p.name, p.age
# 4. 查询张三朋友的朋友
match (:Person{name:'张三'})-[:FRIEND]->(:Person)-[:FRIEND]->(p:Person) return p.name, p.age
# 5. 查询王五朋友生活的国家
match (:Person{name:'王五'})-[:FRIEND]->(p:Person)-[:LIVE_IN]->(c:Country) return c.name, collect(p.name)
# 6. 查询所有 Person 之间的关系
match (p1:Person)-[r]-(p2:Person) return p1.name, type(r), p2.name
match (p1)-[r:FRIEND]-(p2) return p1.name, type(r), p2.name
# 7. 查询具有 LIVE_IN 关系的结点
match (p)-[r:LIVE_IN]->(c) return p.name, type(r), c.name
- 约束操作 {#title-7} ==================
# 创建索引
create index on:Student(name)
# 删除索引
drop index on:Student(name)
# 创建唯一索引
create constraint on (s:Teacher) assert s.name is unique
# 删除唯一索引
drop constraint on (s:Teacher) assert s.name is unique