Neo4j 之安装和 CQL 基本命令学习

正常使用结构化的查询语言 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

链接:https://pan.baidu.com/s/1uwkT0SDdKlzN8C2kNRBKhA?pwd=xq4w 

然后就是点击安装就可以了,安装好以后记得设置环境变量(根据你自己的安装路径设置)。

neo4j安装

链接:https://pan.baidu.com/s/11aLfX2FlD7Accra5FyUmOw?pwd=dt8q 

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

表示要返回的结果集数量,表示要跳过的结果集数量。使用 LIMIT 和 SKIP 子句时,返回的结果集将会是从跳过指定数量的结果集之后的前  个结果集。 

如果返回的路径,则 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'})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值