正常使用结构化的查询语言 SQL(Structured Query Language)较多一些,但是像 Neo4j 这种非结构化的图形数据库来说,就不得不学习下 CQL(Cypher Query Language)语言了。如果你之前学过 《离散数学》或《图论》,对语法理解起来应该要容易一些。
Neo4j 安装
jdk安装
我用的 Neo4j 是 neo4j-community-3.5.5-windows.zip ,所以下载个 jdk11 安装就可以了。官网的下载比较卡,网盘没失效的话可以用这个链接。
jdk-11.0.6_windows-x64_bin.exe
然后就是点击安装就可以了,安装好以后记得设置环境变量(根据你自己的安装路径设置)。
neo4j安装
neo4j 的安装也很简单,解压后,放到某个目录(目录不要有特殊字符),我自己是直接放到 C 盘下的,然后设置好环境变量就可以了。
命令行脚本启动以后,打开访问下面标记处远程链接就可以了
默认账号和密码都是 neo4j ,初次登录要修改密码,这个就不多说了。
CQL基本命令
详细命令文档可以参考官方文档:https://www.cnblogs.com/MarisaMagic/p/17537963.html
常用命令关键词不多,主要是下面这些。
节点操作
创建节点
创建节点的基本语法如下:
CREATE (node_name:label_type {property:value});
CREATE
: 创建新节点。node_name
: 节点的名称。label_type
: 节点所属的标签类型。property:value
: 节点属性和值。
我们来个例子试试,比如创建一个人员节点。当然, RETURN 语句不是必须的,如果你不需要查看创建的结果,就不需要执行 RETURN 语句返回。
CREATE (person:Person {name: "John", age: 30 })
RETURN person;
对于语句中的 person,如果你后续没有针对它的引用,其实也是可以不用写的,当然写上之后要更清晰一些。像下面的语句,同样可以成功创建节点。
CREATE (:Person {name: "Looking", age: 30 })
批量创建多个节点
CREATE (person1:Person {name: "John", age: 30 }), (person2:Person { name:"Sandra", age: 25 });
创建不带标签的节点
CREATE (n {name: "No_Label"})
RETURN n
查询节点
查询节点的基本语法如下:
MATCH (node_name:label_type)
WHERE node_name.property = value
RETURN node_name;
比如查询所有 Person 的节点:
MATCH (person: Person) return person;
也可以指定属性查询
MATCH (person: Person{name:"John"}) return person;
或者使用 WHERE 语句指定查询条件
MATCH (person: Person)
WHERE person.name="John"
RETURN person;
修改节点
修改节点主要用到 SET 关键字,这块和 SQL 的用法差不多,语法如下:
MATCH (node_name:label_type {property:value}) SET node_name.new_property = new_value;
比如修改 Person 节点中名为 John 的 age 为 100 。
MATCH (person: Person{name:"John"}) SET person.age=100;
也可以同时修改多个属性,中间用逗号隔开即可。
注意:即使我新建 John 节点的时候没有给他指定 phone 属性,但是丝毫不影响我修改节点时给他加上一个 phone 属性。
MATCH (person: Person{name:"John"})
SET person.age=100, person.phone='12345';
删除节点
删除节点也很简单,显示用 MATCH 查询节点,然后对查询结果的句柄使用 DELETE 删除即可
MATCH (person: Person{name:"John"}) DELETE person;
如果节点还有关系的话,直接删除节点是会报错的——这个比较好理解,关系是建立在节点的基础上的,如果节点没了,那么和这个节点关联的关系如何自处?
当然还可以使用带 WHERE 语句的复杂查询 。
MATCH (person: Person) WHERE person.age>25 DELETE person;
关系操作
在图当中,除了针对节点的操作,各个节点之间还有相应的关系。
创建关系
在Neo4j中,关系是将两个节点连接在一起的东西。要在Neo4j中创建关系,需要使用以下语法:
CREATE (node_name_1:label_type)-[:relationship_type {property:value}]->(node_name_2:label_type);
比如,我们给 John 和 Sandra 之间创建一个朋友关系 FriendWith
CREATE (person1:Person{name: "John"})-[r:FriendWith{year:3}]->(person2:Person{name: "Sandra"})
创建关系的时候,则会自动创建和关系关联的节点(这里以 Python 连接图形数据库进行创建操作为例):
from py2neo import Graph
graph = Graph("bolt://localhost:7687", auth=("neo4j", "Neo4j"))
def create_knowledge_graph():
graph.run("""CREATE (a:Person {name: 'Alice'})-[:FriendWith {years: 5}]->(b:Person {name: 'Bob'})""")
if __name__ == '__main__':
create_knowledge_graph()
当然,也可以使用 MATCH 和 MERGE 的组合来创建关系(注意箭头方向,我这里创建的一个反向关系,而且 FriendWith 关系是双向的也完全说得通):
MATCH (person1:Person {name: "John"}), (person2:Person {name: "Sandra"})
MERGE (person1)<-[r:FriendWith{years:3}]-(person2);
查询关系
关系查询上面已经有示例了,比如我要查询 John 和 Sandra 之间的关系:
MATCH p=(:Person{name: "John"})-[r:FriendWith]-(:Person{name: "Sandra"}) RETURN p
也可以通过 WHERE 语句对节点和关系的属性进行限制:
MATCH p=(person1:Person)-[r:FriendWith]-(person2:Person)
WHERE person1.name="John"
AND person2.name="Sandra"
AND r.years=3
RETURN p
修改关系
通节点一样,关系的熟悉修改也使用 SET,比如我们修改 John 和 Sandra 的好友关系时间为 5 年
MATCH p=(person1:Person)-[r:FriendWith]-(person2:Person)
WHERE person1.name="John"
AND person2.name="Sandra"
SET r.years=5
RETURN p
删除关系
MATCH p=(person1:Person{name: "John"})-[r:FriendWith]-(person2:Person{name: "Sandra"})
DELETE r
注意:上面的删除语句,如果使用 delete p 删除查询结果的链路网的话,会将节点和关系一并删除掉的哟!
看下面语句的执行结果,节点也被删除了,所以使用时千万要小心。
如果删除链路网的时候,链路网中的节点还有其他关系,使用 delete p 会怎么样呢?
我们可以看到,除了 MATCH 匹配到的链路网,如果节点之间还有其他关系,删除链路的话是会报错的哟。
但是我们可以用 detach delete p,删除查询的链路网及所有和链路网有直接关联的关系。如下例:
全网:
子网:
执行删除语句删除子网及与子网有直接关联的关系:
match p=(n1:PersonNode{name:"Sandra"})-[r*1..2]-(n2) detach delete p
剩余网:
链路查询
查询两个节点间的所有链路(生产环境数据量很多的时候,最好限定度数,比如:r*1..3 限定链路的关系在 1 到 3 度,否则容易产生超网然后超时)
match p=(n1:Person{name: "test1"})-[*]-(n2:Person{name: "test2"})
with reduce(s="", node in nodes(p) | s + '->' + node.name) as path
return substring(path, 2, length(path))
常用操作
删除操作(DELETE)
一般图数据库中,除了离散的节点,还有和节点相关联的关系,当节点还有关系时,单独删除节点并不可行,此时,还需要将和删除节点相关的关系都一并删除才行。
比如,我们要删除 Sandra 以及和 Sandra 相关联的关系 (r 后面没有添加关系标签,是为了让 r 匹配和 Sandra 相关的任何关系):
MATCH (person:Person{name: "Sandra"})
OPTIONAL MATCH (person)-[r]-()
DELETE person, r
从返回结果可以看到,Sandra 节点以及和它相关的 4 个关系已被删除
删除所有节点和关系(谨慎操作)
先使用 match 匹配所有节点,再使用 optional match 匹配和节点相关的所有关系,最后再将匹配的节点和关系使用 delete 一并删除。
MATCH (n)
OPTIONAL MATCH (n)-[r]-()
DELETE n, r
或
MATCH (n)
DETACH DELETE n
删除所有孤立节点
MATCH (n)
DELETE n
删除所有关系
MATCH (n)
OPTIONAL MATCH (n)-[r]-()
DELETE r
移除属性 (REMOVE)
比如我们移除 John 的 age 属性:
MATCH (person:Person{name: "John"})
REMOVE person.age
移除标签
MATCH (n:Person{name: "Looking"})
REMOVE n:Person
RETURN n
排序操作 (ORDER BY)
比如将人员查询结果按照年龄降序排序:
MATCH (p:Person)
RETURN p
ORDER BY p.age DESC
结果合并(UNION 和 UNION ALL)
MATCH (p:Person{name: "John"})
RETURN p
UNION
MATCH (p:Person)
WHERE p.age=40
RETURN p
查询和 John 有好友和家庭关系的的节点名称:
UNION ALL 不会去重
MATCH (n:Person {name: 'John'})-[:FamilyWith]->(f)
RETURN f.name AS name
UNION ALL
MATCH (n:Person {name: 'John'})-[:FriendWith]->(f)
RETURN f.name AS name;
UNION 会去重
MATCH (n:Person {name: 'John'})-[:FamilyWith]->(f)
RETURN f.name AS name
UNION
MATCH (n:Person {name: 'John'})-[:FriendWith]->(f)
RETURN f.name AS name;
偏移和限制(LIMIT 和 SKIP)
MATCH (n)
RETURN n.property
SKIP m
LIMIT n
n
表示要返回的结果集数量,m
表示要跳过的结果集数量。使用 LIMIT
和 SKIP
子句时,返回的结果集将会是从跳过指定数量的结果集之后的前 n
个结果集。
如果返回的路径,则 n 表示对返回的一度路径限定到 n 条。
简单理解 SKIP 类似于 SQL 的 OFFSET,表示偏移量, LIMIT 同 SQL 的 LIMIT
不加限制时
MATCH (p:Person)
RETURN p
限制 LIMIT
MATCH (p:Person)
RETURN p
LIMIT 2
限制 SKIP 和 LIMIT
MATCH (p:Person)
RETURN p
SKIP 1
LIMIT 2
FOREACH
MATCH p=(start)-[*]->(finish)
WHERE start.name = 'A' AND finish.name = 'D'
FOREACH (n IN nodes(p) | SET n.marked = true)
MERGE
MERGE 前面的例子有使用过,MERGE
是一种用于创建或更新节点和关系的关键字。它可以用于合并现有的节点和关系,也可以用于创建新的节点和关系。 可以简单理解为 SQL 中的 UPSERT(UPDATE + INSERT),如果查询到的关系和节点已存在则更新,否则创建。
就比如节点创建时,我们先试试用 MREGE 创建同名 Looking 节点,与原 Looking 节点属性完全保持一致(发现没有变化)。
MERGE (p:Person{name: "Looking", age: 50})
当然,如果新节点的属性和旧节点属性不完全一致,使用 MERGE 也是会创建新的节点的。
我们再试试用 MREGE 创建同名 Looking 节点(创建成功) :
CREATE (p:Person{name: "Looking", age: 50})
这两个 Looking 节点的属性完全一样(当然,系统为了区分,对应的 id 不一样)。
使用 MERGE 创建关系也类似,使用 CREATE 会始终创建新的关系。
我们使用 CREATE 语句再次创建 John 和 Sandra 之间的 FriendWith 关系,关系属性和之前保持完全一致,再看看是什么结果。
MATCH (person1:Person {name: "John"}), (person2:Person {name: "Sandra"})
CREATE (person1)-[r:FriendWith{years:3}]->(person2);
可以看到,创建语句执行后,John 和 Sandra 之间有两个 FriendWith 关系。
MERGE ... ON CREATE
没有则创建节点,并设置节点属性。
MERGE (n:Person{name:"name1"})
ON CREATE SET n.exists=True
MERGE ... ON MATCH
如果找到节点就设置属性,否则仅创建节点。
MERGE (n:Person{name:"name1"})
ON MATCH SET n.exists=True
NULL
-
Neo4j CQL将空值视为对节点或关系的属性的缺失值或未定义值。
-
当我们创建一个具有现有节点标签名称但未指定其属性值的节点时,它将创建一个具有NULL属性值的新节点。
-
还可以用 NULL 作为查询的条件。
比如我已经删除了 Looking 节点的 age 属性。
MATCH (p:Person)
WHERE p.age IS NULL
RETURN p
IN
Neo4j CQL提供了一个 IN 运算符,以便为 CQL 命令提供值的集合判断。
MATCH (p:Person)
WHERE p.age IN [30, 40]
RETURN p
CASE
MATCH (n:Person)
RETURN
CASE
WHEN n.name='John' THEN "hello " + n.name
WHEN n.age>50 THEN "old age"
ELSE "Default value"
END AS
RESULT
Map
字面值映射
return {key: "value", list_key:[{inner:"map1"}, {inner:"map2"}]}
{
"list_key": [
{
"inner": "map1"
},
{
"inner": "map2"
}
],
"key": "value"
}
map投射
WITH
使用 with 可以将结果传递到后续查询之前对结果进行操作,比如改变结果的形式或者数量。
使用 collect 前对结果进行排序
MATCH (n:Person)
WITH n
ORDER BY n.name DESC LIMIT 3
RETURN collect(n.name)
限制路径搜索的分支
MATCH (n {name: "Looking"})--(m)
WITH m
ORDER BY m.name DESC LIMIT 1
MATCH (m)--(o)
RETURN o.name
更新语句中变量的属性
MATCH (n:Person{name: "Looking"})-[:FriendWith]-(friend)
WITH n, count(friend) AS c
SET n.friendCount = c
RETURN n.friendCount
MATCH (n:Person{name: "Looking"})-[:FriendWith]-(friend)
WITH n, count(friend) AS c
WHERE c>3
RETURN n, c
UNWIND
将一个 List 转为一个结果集
WITH [[1, 2], [3, 4], 5] AS nested
UNWIND nested AS x
RETURN x
x
[1, 2]
[3, 4]
5
二次 unwind
WITH [[1, 2], [3, 4], 5] AS nested
UNWIND nested AS x
UNWIND x AS y
RETURN y
y
1
2
3
4
5
创建唯一列表
WITH [1, 1, 2, 2] AS coll
UNWIND coll AS x
WITH DISTINCT x
RETURN collect(x) AS set
set
[1, 2]
路径操作
获取节点间的最短路径
match p=shortestPath((n1:Person{name: "Looking"})-[*1..2]- (n2:Person{name: "Sandra"}) )
return p
获取所有最短路径
match p=allShortestPaths((n1:Person{name: "Looking"})-[*1..2]- (n2:Person{name: "Sandra"}) )
return p
字符串匹配操作
STARTS WITH
MATCH (p) WHERE p.name STARTS WITH "Jo" RETURN p
ENDS WITH
MATCH (p) WHERE p.name ENDS WITH "ing" RETURN p
CONTAINS
MATCH (p) WHERE p.name CONTAINS "ndr" RETURN p
正则匹配
使用 =~ 进行正则模式匹配
MATCH (n:Person)
WHERE n.name =~ ".*oo.*"
RETURN n
布尔运算操作
AND
MATCH (p) WHERE size(p.name)>4 AND p.age>45 RETURN p
OR
MATCH (p) WHERE size(p.name)>4 OR p.age>45 RETURN p
XOR
异或操作,两边真值相同则为假,不同则为真。
MATCH (p) WHERE p.name="Looking" XOR p.age<60 RETURN p
可以看到,Looking 节点两个条件都满足,但是最后查询结果并没有
NOT
MATCH (p) WHERE NOT p.name="Looking" RETURN p
索引和约束
创建索引
create index on :Person(name)
删除索引
drop index on :Person(name)
创建唯一约束
人名唯一
create constraint on (p:Person) assert p.name is unique
创建存在性约束
人名属性必须存在
create constraint on (p:Person) assert exists(p.name)
create constraint on ()-[r:FriendWith]-() assert exists(r.years)
删除约束
drop constraint on (p:Person) assert p.name is unique
查看已有的索引和约束
call db.indexes();
call db.constraints();
查询指定索引
match (p:Person{name: "Looking"})
using index p:Person(name)
return p
标签反向过滤
match (n) where not (n:CompanyNode or n:PersonNode)
匹配多种关系类型
match p=(n1:CompanyNode)-[r:Branch | :SeniorExecutive]-(n2)
return p
变长路径
match p=(n1:PersonNode{name: "Looking"})-[r:Friend*1..3]-(n2)
return p
函数操作
谓词函数
exists()
如果指定的模式存在于图中,或者特定的属性存在于节点、关系中,则返回 True 。
MATCH (p:Person)
WHERE exists(p.age)
RETURN p
查询有 age 属性的节点
MATCH (n)
WHERE exists(n.name)
RETURN n.name as name, exists((n)-[:FriendWith]-()) AS has_friend
name has_friend
"John" true
"Sandra" true
"Looking" true
列表获取函数
keys(node):从节点属性中抽取属性键
labels(node):节点标签的列表
nodes(path):从路径中获取所有节点的列表
relationships(path):从路径中获得所有的关系的列表
keys()
labels()
返回查询节点的 属性 字段名称和 节点标签名称:
MATCH (p:Person{name: "John"}) RETURN keys(p), labels(p)
nodes()
relationships()
返回查询路径的节点和关系列表:
MATCH p=()-[]-() RETURN nodes(p), relationships(p)
集合检查函数
节点 和 关系 的概念都比较好理解,这里讲一下路径:简单理解一条路径或链路由两个节点和一个关系组成。
类型 | 含义 |
---|---|
Node | 节点 |
Relationship | 关系 |
Path | 链路或路径(指的是节点与连接节点的关系所共同组成的概念) |
all()
all() 表示所有的元素都满足条件才会返回 True。
先给 John 设置 array 属性,对应的值为 [1, 2, 3, 4, 5]
match (p:Person{name: "John"}) SET p.array = [1, 2, 3, 4, 5]
比如查询 Person 节点中 array 属性值的元素均大于 0 的节点
MATCH (p:Person)
WHERE all(e in p.array WHERE e>0)
RETURN p
match p=(a:CompanyNode)-[r*1..3]-()
where a.credit_no='xxxx' and all(i in r where type(i) in ["SeniorExecutive", "Invest", "ShareHolder", "Email"])
return p
any()
any() 表示至少一个元素满足条件就返回 True。
比如查询所有路径当中,对应路径中节点的 age 至少有一个大于 40 路径:
MATCH p=()-[]-()
WHERE any(n in nodes(p) WHERE n.age>40)
RETURN p
因为路径的查询不仅仅包含关系本身,还包含和关系相关的节点,所以展示时,除了会展示满足要求的节点,还会返回和满足要求相关联的节点。
这里也顺便说一下和查询关系的 nodes() 和 relationships() 函数。
nodes() 返回查询路径中的节点列表, relationships() 返回查询路径中的关系的列表。
下面语句中的 p 可以认为是查询结果路径的句柄。
MATCH p=()-[]-() RETURN nodes(p), relationships(p)
none()
none() 函数表示没有一个元素满足条件则返回 True。
比如查询结果中节点 age 均不为 40 的路径。
MATCH p=()-[]-()
WHERE none(n in nodes(p) WHERE n.age=40)
RETURN p
single()
single() 表示只有一个元素满足条件。
我再创建了一个 Alice 节点,age 也为 30,且建立了和 John 的关系。
然后使用 single 查询结果中节点 age 只有一个为 30 的路径。
MATCH p=()-[r]-()
WHERE single(n in nodes(p) WHERE n.age=30)
RETURN p
可以看到, 节点 Alice 并没有出现在路径的结果当中(因为 Alice 和 John 的 age 均为 30, 因此 Alice 和 John 的路径不满足要求)。
标量函数
id()
返回节点或关系的 id
节点和关系的属性 properties 或许可能会完全一样,但是数据库为了区分,不同节点和关系的 id 一定是不一样的。
properties()
关系函数
type()
返回关系的类型
MATCH p=(n:Person{name: "John"})-[r]-() RETURN type(r)
startNode()
endNode()
返回关系的开始节点和结束节点
MATCH p=(n:Person{name: "John"})-[r]-() RETURN startNode(r), endNode(r)
列表相关
head()
last()
size()
MATCH (p:Person{name: "John"}) RETURN head(p.array), last(p.array), size(p.array)
coalesce()
coalesce() 返回所有表达式中第一个不为 null 的值
MATCH (p:Person{name: "John"}) SET p.key=coalesce(null, "", "key")
根据示例结果,可知 coalesce(null, "", "key") 返回的是空字符串 ""
MATCH (p:Person{name: "John"}) RETURN p.key
列表过滤
extract()
抽取值组成列表,比如抽取路径节点的 name 作为列表(每条路径有两个节点,所以每行有两个节点的 name ):
MATCH p=()-[]-() RETURN extract(n in nodes(p) | n.name) as extracted
抽取路径的关系作为列表:
MATCH p=()-[]-() RETURN extract(r in relationships(p) | r.years) as extracted
filter()
过滤列表元素组成新列表,比如查询结果中,对某个节点的属性进行过滤并返回过滤后的结果。
MATCH (p:Person{name: "John"}) RETURN p.array, filter( e in p.array WHERE e>3) as filtered
聚合函数
count()
avg()
max()
min()
sum()
collect()
聚合函数默认去掉为 NULL 的值。
MATCH (p:Person) RETURN count(p.age), avg(p.age), max(p.age), min(p.age), sum(p.age), collect(p.age)
如果使用 distinct 关键字,会对元素去重后再进行聚合操作。
字符串函数
转化为大写 UPPER()
转化为小写 LOWER()
截取字符串 SUBSTRING(),substring(a.name, m, n) 从索引 m 开始,截取 n 位
替换 REPLACE(), replace(a.name,'old', 'new')
MATCH (p:Person{name: "John"})
RETURN p.name, upper(p.name), lower(p.name), substring(p.name, 0, 2), replace(p.name, 'oh', "xx")
upper()、toUpper()
lower()、toLower()
substring()
replace()
left()
RETURN left("hello world", 5)
返回original自左起共length个字符,字符顺序不变。
- left(null, length)或 left(null, null)会返回null;
- left(original, null)会返回一个错误;
- length不是整数时会返回一个错误;
- length比original的总长度还长时,返回original本身;
reverse()
字符串或序列反转
RETURN reverse("hello world"), reverse([1, 2, 3, 4, 5])
trim()
去除字符串两边空白
RETURN trim(" hello world")
split()
字符串分隔成列表
RETURN split("hello world", " ")
序列生成函数
range()
range() 生成的序列包含了首位的元素
RETURN range(1, 10)
其他操作和 Python 的列表推导操作类似 [x for x in range(10)]
RETURN range(0, 10)[1]
1
RETURN range(0, 10)[1..3]
[1, 2]
RETURN range(0, 10)[-4]
7
RETURN range(0, 10)[..4]
[0, 1, 2, 3]
RETURN range(0, 10)[..-6]
[0, 1, 2, 3, 4]
RETURN [x in range(0, 10) WHERE x%2=0] as result
[0, 2, 4, 6, 8, 10]
RETURN [x in range(0, 10) | x^2] as result
[0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0, 100.0]
reverse()
序列逆序
RETURN reverse(range(1, 10))
文件导入(csv)
批量创建节点
有标题
从文件创建节点和关系很方便快捷,特别是针对节点或关系很多的时候,能避免我们自己去重复写创建和循环语句,下面就如何从 csv 文件创建节点做一个简单介绍。
比如我有 test_n.csv 文件,需要将里边的字段作为节点属性,行作为节点数据导入到图数据库:
name,age
John,30
Sandra,40
Looking,50
首先需要将 csv 文件拷贝到 neo4j的上传目录(我的路径在 C:\neo4j-community-3.5.5\import)
然后使用如下导入语句即可成功导入(记录文件路径要加上引号)。
LOAD CSV WITH HEADERS FROM "file:///test_n.csv" as f
MERGE (p:Person{name: f.name, age: f.age})
但是还有一点,这样直接从文件读取插入的节点,属性都统一被设置成了字符串的形式。
要想将 age 以整形的数据类型导入,则需要对创建语句进行稍微改动一下(使用 toInteger() 函数进行强制类型转换):
LOAD CSV WITH HEADERS FROM "file:///test.csv" as f
MERGE (p:Person{name: f.name, age: toInteger(f.age)})
无标题
若 csv 文件第一行不是标题,如 test.csv 为:
John,30
Sandra,40
Looking,50
创建语句去掉 WITH HEADERS ,同时使用索引的方式取属性即可:
LOAD CSV FROM "file:///test.csv" as f
MERGE (p:Person{name: f[0], age: toInteger(f[1])})
批量创建关系
上面的示例演示了如何从文件创建节点,基于上面创建的节点,下面演示下如何批量创建节点之间的关系。
上面创建的节点包含了 name 和 age 属性,我们以 name 为索引对节点进行查询。
test_r.csv 文件为(name1 和 name2 为节点的 name,years 表示两人的好友关系的时间):
name1,name2,years
John,Sandra,3
John,Looking,3
Looking,Sandra,5
我们基于新的 test.csv 创建 Person 节点之间的 FriendWith关系,属性为 years:
LOAD CSV WITH HEADERS FROM "file:///test_r.csv" as f
MATCH (p1:Person{name: f.name1}), (p2:Person{name: f.name2})
MERGE p=(p1)-[r:FriendWith{years: f.years}]->(p2)
文件导出
neo4j-apoc-procedures文档:https://neo4j-contrib.github.io/neo4j-apoc-procedures/
使用neo4j-apoc-procedures步骤:
- 1、下载与Neo4j相应版本的jar包:https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases
- 2、把jar包放在安装目录的plugins文件夹下
- 3、在配置文件neo4j.conf里添加
- apoc.export.file.enabled=true
- apoc.import.file.enabled=true
- dbms.security.procedures.unrestricted=apoc.*
- 4、重启Neo4j服务
- 5、在可视化界面运行:return apoc.version(),如果出现对应的版本号,证明安装成功
然后再执行查询语句并导入到 csv 文件即可,导出的文件默认在 neo4j 软件的 import 目录下。
如:C:\neo4j-community-3.5.5\import
with "match (n:Person) return n.name as name" as query
call apoc.export.csv.query(query, "query.csv", {})
YIELD file, source, format, nodes, relationships, properties, time, rows, batchSize, batches, done, data
RETURN file, source, format, nodes, relationships, properties, time, rows, batchSize, batches, done, data;
编程接口
我自己以 Python 的 py2neo 包来进行编程操作图数据库。
需要先 pip install py2neo 安装相应的模块。
创建节点和关系
from py2neo import Graph, Node, Relationship
graph = Graph("bolt://localhost:7687", auth=("neo4j", "Neo4j"))
if __name__ == '__main__':
alice = Node("Person", name="Alice")
bob = Node("Person", name="Bob")
alice_friend_bob = Relationship(alice, "FriendWith", bob, years=3)
graph.create(alice_friend_bob)
查询节点和关系
from py2neo import Graph, Node, Relationship, NodeMatcher, RelationshipMatcher
graph = Graph("bolt://localhost:7687", auth=("neo4j", "Neo4j"))
if __name__ == '__main__':
node_matcher = NodeMatcher(graph)
persons = node_matcher.match("Person") # 查询所有 Person 节点
for person in persons:
print(person)
# (_1:Person {name: 'Bob'})
# (_36:Person {age: 30, name: 'John'})
# (_37:Person {age: 50, name: 'Looking'})
# (_41:Person {name: 'Alice'})
# (_43:Person {age: 40, name: 'Sandra'})
print()
alice = node_matcher.match("Person", name="Alice").first() # 查询 name 为 Alice 的 Person 节点
print("alice: ", alice)
# alice: (_41:Person {name: 'Alice'})
print()
relationship_matcher = RelationshipMatcher(graph)
friends = relationship_matcher.match(r_type="FriendWith").where()
for friend in friends:
print(friend)
# (John)-[:FriendWith {years: '3'}]->(Looking)
# (John)-[:FriendWith {years: '3'}]->(Sandra)
# (Looking)-[:FriendWith {years: '5'}]->(Sandra)
# (Alice)-[:FriendWith {}]->(Bob)
print()
john = node_matcher.match("Person", name="John").first() # 查询 name 为 John 的 Person 节点
john_friends = relationship_matcher.match([john], r_type="FriendWith").where()
for friend in john_friends:
print(friend)
# (John)-[:FriendWith {years: '3'}]->(Looking)
# (John)-[:FriendWith {years: '3'}]->(Sandra)
更新节点和关系属性
from py2neo import Graph, Node, Relationship, NodeMatcher, RelationshipMatcher
graph = Graph("bolt://localhost:7687", auth=("neo4j", "Neo4j"))
if __name__ == '__main__':
# 查询Alice节点
node_matcher = NodeMatcher(graph)
alice = node_matcher.match("Person", name="Alice").first()
print(alice) # (_41:Person {age: 100, name: 'Alice'})
# 更新Alice节点的属性
alice["age"] = 50
graph.push(alice)
# 再次查询
alice = node_matcher.match("Person", name="Alice").first()
print(alice) # (_41:Person {age: 50, name: 'Alice'})
print()
# 查询 Alice 的一个关系
relation_matcher = RelationshipMatcher(graph)
relation = relation_matcher.match([alice]).where().first()
print(relation) # (Alice)-[:FriendWith {years: 5}]->(Bob)
# 更新关系的属性
relation["years"] = 50
graph.push(relation)
# 再次查询关系
relation_matcher = RelationshipMatcher(graph)
relation = relation_matcher.match([alice]).where().first()
print(relation) # (Alice)-[:FriendWith {years: 50}]->(Bob)
删除节点和关系
from py2neo import Graph, Node, Relationship, NodeMatcher, RelationshipMatcher
graph = Graph("bolt://localhost:7687", auth=("neo4j", "Neo4j"))
if __name__ == '__main__':
node_matcher = NodeMatcher(graph)
alice = node_matcher.match("Person", name="Alice").first()
relationship = RelationshipMatcher(graph)
relation = relationship.match([alice]).where().first()
print(relation) # (Alice)-[:FriendWith {years: 3}]->(Bob)
print(alice) # (_2:Person {name: 'Alice'})
graph.delete(relation) # 目前发现关系删除时,会将关联的节点也一并删除
graph.delete(alice)
原始 cql 语句执行
当然,也可以使用 graph.run 执行原始的 cql 语句来对图库进行相应的操作。
from py2neo import Graph
graph = Graph("bolt://localhost:7687", auth=("neo4j", "Neo4j"))
def run_graph_cql():
delete_all = 'match(n) optional match (n)-[r]-() delete n, r'
create_node_cql = 'create(p1:Person{name: "John", age: 30}), (p2:Person{name: "Sandra", age: 40}), (p3:Person{name: "Looking", age: 50})'
create_relationships_cql = f'''
load csv with headers from "file:///test.csv" as f
match (p1:Person{{name: f.name1}}), (p2:Person{{name: f.name2}})
merge p=(p1)-[r:FriendWith{{years: f.years}}]->(p2)
'''
create_relationship_cql = "merge (a:Person {name: 'Alice'})-[:FriendWith {years: 5}]->(b:Person {name: 'Bob'})"
# cql = 'match (p:Person) where p.name="John" return p'
match_all_node = "match (p) return p"
match_all_path = "match p=()-[]->() return relationships(p) as relationships"
return graph.run(match_all_path)
if __name__ == '__main__':
data = run_graph_cql()
while data.forward():
cursor = data.current
for relation in cursor['relationships']:
start_node = relation.start_node
start = dict(start_node)
start["id"] = start_node.identity
start["label"] = str(start_node.labels).strip(":")
relationships = dict(relation.relationships[0])
relationships["label"] = list(relation.types())[0]
end_node = relation.end_node
end = dict(end_node)
end["id"] = end_node.identity
end["label"] = str(end_node.labels).strip(":")
print((start, relationships, end))
# ({'name': 'Looking', 'age': 50, 'id': 44, 'label': 'Person'}, {'years': '5', 'label': 'FriendWith'}, {'name': 'Sandra', 'age': 40, 'id': 39, 'label': 'Person'})
# ({'name': 'John', 'age': 30, 'id': 38, 'label': 'Person'}, {'years': '3', 'label': 'FriendWith'}, {'name': 'Sandra', 'age': 40, 'id': 39, 'label': 'Person'})
# ({'name': 'John', 'age': 30, 'id': 38, 'label': 'Person'}, {'years': '3', 'label': 'FriendWith'}, {'name': 'Looking', 'age': 50, 'id': 44, 'label': 'Person'})