前言
上一章讲到了Neo4j下载安装过程以及Neo4j浏览器的简单用法,这里通过实战详细说一下Neo4j图数据库具体用法,主要分为增删改查四项。
1、增加
先来个实战,了解一下 Neo4j 的大概用法,实战内容:绘制一个包含简单人物、地理位置及之间所包含关系的图谱。
1.1 第一步:删除当前所有节点
我们删除数据库中以往的图,确保一个空白的环境进行操作:
MATCH (n) DETACH DELETE n
其中,MATCH 是匹配操作,而小括号()代表一个节点 node,括号里面的 n 为标识符或者说变量,这个语句含义:匹配所有节点(因为对节点没有做限制,所有是所有节点),然后删除匹配成功的所有节点。
执行完以后可以点击数据库信息,发现所有节点已经消失。
1.2 第二步:创建人物节点
先来创建一个众所周知的人物:张三
CREATE (p1:Person {name:'张三'}) RETURN p1
执行完成后:
CREATE 是创建操作,Person 是标签,代表节点的类型。花括号{}代表节点的属性,属性类似 Python 的字典,return 的含义是创建完成,在显示区域展示出变量 p1——也就是将人物张三显示出来(可以理解为自带的查询功能)。这条语句的含义就是创建一个标签为 Person 的节点 p1,该节点具有一个 name 属性,属性值是张三。
注意:代码中 p1 是局部变量,执行完就释放了,不会在数据库中存储,只是用来代表节点——用在后面执行 return,可以随意命名。
创建过程中,我发现可以创建多个相同人物:
同时,标签和属性都可以是中文:
CREATE (p1:人物 {名称:'张三'}) RETURN p1
当然,很多编程中直接用中文可能会出现问题,建议还是使用英文比较好。
创建多个人物:
CREATE (p1:Person {name:'张三'})
CREATE (p2:Person {name:'李四'})
CREATE (p3:Person {name:'王五'})
CREATE (p4:Person {name:'陈六'})
CREATE (p5:Person {name:'小明'})
执行结果:
执行结束,在左侧会显示出来:
如上图,当前具有节点为 5 个,当前拥有标签为 person,点击 person 会在右边显示出节点信息。
这里有一个问题,就是在执行多行语句,并且都有 return 命令的情况:
CREATE (p1:Person {name:'张三'}) RETURN p1
CREATE (p2:Person {name:'李四'}) RETURN p2
CREATE (p3:Person {name:'王五'}) RETURN p3
CREATE (p4:Person {name:'陈六'}) RETURN p4
CREATE (p5:Person {name:'小明'}) RETURN p5
执行上述命令会报错,因为没有分号,表示一个执行命令(只是会相继执行),但是却存在 5 个返回结果。
正确方案:
#方案1:去掉return
CREATE (p1:Person {name:'张三'})
CREATE (p2:Person {name:'李四'})
CREATE (p3:Person {name:'王五'})
CREATE (p4:Person {name:'陈六'})
CREATE (p5:Person {name:'小明'})
方案2:每句增加分号
CREATE (p1:Person {name:'张三'}) RETURN p1;
CREATE (p2:Person {name:'李四'}) RETURN p2;
CREATE (p3:Person {name:'王五'}) RETURN p3;
CREATE (p4:Person {name:'陈六'}) RETURN p4;
CREATE (p5:Person {name:'小明'}) RETURN p5
不只是 return,之前还提到过相同变量也会存在上述问题,比如:
# 执行下述命令的时候,会一起执行,但是一个变量P相当于对应了多个节点,会冲突,解决方法和上述相同,要么加分号,要么更改变量名
CREATE (p:Person {name:'张三'})
CREATE (p:Person {name:'李四'})
CREATE (p:Person {name:'王五'})
CREATE (p:Person {name:'陈六'})
CREATE (p:Person {name:'小明'})
1.3 第三步:创建地理节点
节点名称为 location,属性包括城市(city)和级别(level):
CREATE (c1:Location {city:'北京', level:'直辖市'})
CREATE (c2:Location {city:'山东', level:'省份'})
CREATE (c3:Location {city:'深圳', level:'直辖市'})
CREATE (c4:Location {city:'上海', level:'直辖市'})
CREATE (c5:Location {city:'河北', level:'省份'})
运行结果如下:
如上图所示,不同类型的节点采用了不同颜色标识出来。
1.4 创建关系
1.4.1 人与人之间的关系
MATCH (p1:Person {name:'张三'}),
(p2:Person {name:'李四'})
MERGE (p1)-[f:朋友]->(p2)
结果如下:
如上所示,看完结果再看代码就非常容易理解了:
- Match 会将匹配数据库中节点类型为 person,而 name 属性为张三的,将其指向 p1;p2 指向李四
- merge 会将匹配后的结果,使用[关系变量名:关系变量值]创建朋友关系,这里的方括号[]即为关系,f 为关系的名称,朋友为关系的类型。注意这里的箭头–>是有方向的,表示是从 p1 到 p2 的关系。
ok,再增加几个关系:
MATCH (p1:Person {name:'张三'}),
(p2:Person {name:'王五'})
MERGE (p1)-[f:朋友]->(p2);
MATCH (p1:Person {name:'王五'}),
(p2:Person {name:'陈六'})
MERGE (p1)-[x:兄弟]->(p2);
MATCH (p1:Person {name:'陈六'}),
(p2:Person {name:'小明'})
MERGE (p1)-[q:妻子]->(p2);
结果如下:
问题一:如果存在多个相同人物,建立关系会如何?
既然 match 是匹配数据库中的人物,那么如果有多个张三怎么办呢?
我们来实践一下,将之前的关系删除,创建两个张三,两个个李四:
MATCH (n) DETACH DELETE n;
CREATE (p1:Person {name:'张三'})
CREATE (p2:Person {name:'李四'})
CREATE (p3:Person {name:'张三'})
CREATE (p4:Person {name:'李四'})
注意:上面第一行要加分号,表明要单独执行,执行结果如下:
创建张三与李四的关系:
MATCH (p1:Person {name:'张三'}),
(p2:Person {name:'李四'})
MERGE (p1)-[f:朋友]->(p2)
执行结果如下:
如上图所示,该结果说明了
- 匹配的时候,变量 p1 可以指向多个节点
- 建立关系的时候,可以建立多个关系
问题二:创建关系的变种命令
事实上,当我看到创建关系的那条语句时,我就想到了下面这一条命令:
MERGE (p1:Person {name:'张三'})-[:朋友]->(p2:Person {name:'李四'})
两者是不是等同的呢?我们来执行一下,还是先删除以前的节点,然后创建一个张三,一个李四:
MATCH (n) DETACH DELETE n;
CREATE (p1:Person {name:'张三'})
CREATE (p2:Person {name:'李四'})
执行结果如下:
OK,我们来执行以下命令:
MERGE (p1:Person {name:'张三'})-[f:朋友]->(p2:Person {name:'李四'})
执行结果如下:
执行结果非常让人惊讶,可以看到多出来一个张三和李四,我们回过头来,再看看上面的变种命令,就会想到一个可能:
没有了 match,就不会再匹配数据库中的节点了,而是直接创建新的节点,同时也完成关系的创建。
再来看另外一个变种:
MATCH (p1:Person {name:'张三'}), (p2:Person {name:'王五'}),(p3:Person {name:'王五'}),
(p4:Person {name:'陈六'}),
(p5:Person {name:'小明'})
MERGE (p1)-[:朋友]->(p2),(p3)-[x:兄弟]->(p4),(p1)-[q:妻子]->(p5)
执行后报错如下:
显然,这个变种命令是不行的。
1.4.2 创建人与地理位置的关系
如果仔细看完上一节,那么创建人与地理位置的关系就好说了:
MATCH (p1:Person {name:'张三'}),
(c:Location {city:'北京'})
MERGE (p1)-[l:出生于]->(c)
执行结果如下:
接下来,所有人都建立出生地关系(命令就不写了,仿照上面写就可以):
问题一:大小写问题
在创建人与地理位置的关系时,发现一个问题,就是标签是区分大小写的:
MATCH (p1:Person {name:'李四'}),
(c:location {city:'北京'})
MERGE (p1)-[l:出生于]->(c)
上述代码中,将 Location 写为 location,执行结果如下:
没有任何执行记录,表明在匹配的是就没有匹配成功,问题就在于没有找到 location 这个类型的节点。
标签类型区分大小写,但是执行关键词是不区分大小写的,比如 match,merge 等。
1.4.3 为关系添加属性
为关系添加属性与为节点添加属性类似:
MATCH (a:Person {name:'张三'}),
(b:Location {city:'北京'})
MERGE (a)-[:出生于 {since:2001}]->(b)
2、查询
2.1 查询所有对外有关系
MATCH (n)-->() RETURN n
上述查询属于模糊查询,只要有节点对外有关系,则显示出来,查询结果:
需要注意的是,返回的是节点,而节点之间的关系则是附属的,也就是只要该节点有对外关系(比如李四就有出生于的指向),那么就显示出来,其中地点一个也没有显示出来,因为地点都是被指向的。
我们很容易相对,与上面相对的查询,将 n 换个位置,就是被指向的查询:
MATCH ()-->(n) RETURN n
是不是很 easy!
2.2 查询所有存在关系的节点
MATCH (n)--() RETURN n
只要该节点存在关系,无论是指向还是被指向,都显示出来:
2.3 查询所有对外有关系的节点,以及关系类型
MATCH (n)-[r]->() RETURN n.name, type(r),r.name
注意:这个查询告诉我们如何查询一个节点的属性:节点.属性,还有关系的类型,查询结果:
可以看到关系的名字为 null,这就告诉我们关系只有类型,没有 name,当然了关系还有属性。
我本来还尝试了 type(n),结果报错,显然节点是没有类型的。
2.4 查询所有具有某种关系的节点
MATCH (n)-[:出生于]-() RETURN n
结果如下:
我们还可以将某个变量指向关系,然后查询关系的类型:
MATCH (n)-[r:出生于]-() RETURN n,type(r)
好吧,上面的运行结果与上面的图一致,并没有显示出 type®,猜测是节点和类型不是一个维度的数据,只显示了第一个返回结果。
我们可以将某种关系赋予方向:
MATCH (n)-[r:出生于]->() RETURN n
2.5 查询具有某种属性的节点
MATCH (n:Person{name :'张三'}) RETURN n
也可以使用 where 关键字:
MATCH (n) WHERE n.name = '张三' return n
2.6 查找某人朋友的兄弟的名字
MATCH (n:Person {name:'张三'})-[r1:朋友]-()-[r2:兄弟]-(goal) RETURN goal.name AS who
3、修改/增加
我们记得之前查询过关系的名称,结果返回 null,也就是一个未知的属性是 null,也就是说该属性是存在的,只是为空,我们来验证一下:
MATCH (p:Person {name:'张三'}) return p.age
如果,我们将 null 值修改为其他值,是不是就表示增加了新的属性,测试一下:
MATCH (p:Person {name:'张三'}) set p.age=35
MATCH (p:Person {name:'张三'}) return p.age
结果说明了,在 neo4j 中,修改和增加是一个含义,都是 set 关键字
4、删除
删除和查询一样,都是先匹配关系或者节点,然后利用关键字去删除某个节点或者关系,区别就在于关键字不一样。
Neo4j 的删除关键字有两个:remove 和 delete,在英文中 remove 是移除的含义,这这里表示移除某种属性;而 delete 则是直接删除,删除节点和关系。
4.1 remove
MATCH (p:Person {name:'张三'}) REMOVE p.age
4.2 delete
# 删除节点
match ((p:Person {name:'李四'})-[d:出生于]->(c:Location{city:'北京'})) delete p
# 删除关系
match ((p:Person {name:'李四'})-[d:出生于]->(c:Location{city:'北京'})) delete d
# 删除关系和节点
match ((p:Person {name:'李四'})-[d:出生于]->(c:Location{city:'北京'})) delete p,d,c
#删除某个节点及其关系
MATCH (n) WHERE n.name = '张三' delete n
# 删除所有关系和节点
MATCH (n) DETACH DELETE n