图数据库常规的有:neo4j(支持超多语言)、JanusGraph/Titan(分布式)、Orientdb,google也开源了图数据库Cayley(Go语言构成)、PostgreSQL存储RDF格式数据。
---- 目前的几篇相关:-----
neo4j︱图数据库基本概念、操作罗列与整理(一)
neo4j︱Cypher 查询语言简单案例(二)
neo4j︱Cypher完整案例csv导入、关系联通、高级查询(三)
相关内容可参考:The Py2neo v4 Handbook,还有中文文档:neo4j 0.1.0a documentation 、Neo4j社区
举个简单的例子:
from py2neo import Node, Relationship
a = Node("Person", name="Alice")
b = Node("Person", name="Bob")
ab = Relationship(a, "KNOWS", b)
>>> ab
>>> (alice)-[:KNOWS]->(bob)
新建两个节点a、b,分别具有一个name属性值,还新建a与b之间有向关系ab,ab的label为KNOWS。
其中:
Node 和 Relationship 都继承了 PropertyDict 类,它可以赋值很多属性,类似于字典的形式,例如可以通过如下方式对 Node 或 Relationship 进行属性赋值,接着上面的代码,实例如下:
a['age'] = 20
b['age'] = 21
r['time'] = '2017/08/31'
print(a, b, r)
运行结果:
(alice:Person {age:20,name:"Alice"}) (bob:Person {age:21,name:"Bob"}) (alice)-[:KNOWS {time:"2017/08/31"}]->(bob)
可见通过类似字典的操作方法就可以成功实现属性赋值。
另外还可以通过 setdefault() 方法赋值默认属性,例如:
a.setdefault('location', '北京')
print(a)
>>> (alice:Person {age:20,location:"北京",name:"Alice"})
另外也可以使用 update() 方法对属性批量更新,接着上面的例子实例如下:
data = {
'name': 'Amy',
'age': 21
}
a.update(data)
print(a)
其中包含的节点属性有:
其中连接的属性有:
class Subgraph(nodes, relationships) 子图是节点和关系不可变的集合。
from py2neo import Node, Relationship
a = Node('Person', name='Alice')
b = Node('Person', name='Bob')
r = Relationship(a, 'KNOWS', b)
s = a | b | r
print(s)
>>> ({(alice:Person {name:"Alice"}), (bob:Person {name:"Bob"})}, {(alice)-[:KNOWS]->(bob)})
还可以通过 nodes() 和 relationships() 方法获取所有的 Node 和 Relationship,实例如下:
print(s.nodes())
print(s.relationships())
另外还可以利用 & 取 Subgraph 的交集,例如:
s1 = a | b | r
s2 = a | b
print(s1 & s2)
>>> ({(alice:Person {name:"Alice"}), (bob:Person {name:"Bob"})}, {})
还可以进行一些额外操作:
from py2neo import Node, Relationship, size, order
s = a | b | r
print(s.keys())
print(s.labels())
print(s.nodes())
print(s.relationships())
print(s.types())
print(order(s))
print(size(s))
>>> frozenset({'name'})
>>> frozenset({'Person'})
>>> frozenset({(alice:Person {name:"Alice"}), (bob:Person >>> >>> >>> {name:"Bob"})})
>>> frozenset({(alice)-[:KNOWS]->(bob)})
>>> frozenset({'KNOWS'})
>>> 2
>>> 1
其中子图拥有的属性内容:
Walkable Types是一个拥有遍历功能的子图。最简单的构造就是把一些子图合并起来:
from py2neo import Node, Relationship
a = Node('Person', name='Alice')
b = Node('Person', name='Bob')
c = Node('Person', name='Mike')
ab = Relationship(a, "KNOWS", b)
ac = Relationship(a, "KNOWS", c)
w = ab + Relationship(b, "LIKES", c) + ac
print(w)
>>> (alice)-[:KNOWS]->(bob)-[:LIKES]->(mike)<-[:KNOWS]-(alice)
另外我们可以调用 walk() 方法实现遍历,实例如下:
from py2neo import walk
for item in walk(w):
print(item)
>>>
(alice:Person {name:"Alice"})
(alice)-[:KNOWS]->(bob)
(bob:Person {name:"Bob"})
(bob)-[:LIKES]->(mike)
(mike:Person {name:"Mike"})
(alice)-[:KNOWS]->(mike)
(alice:Person {name:"Alice"})
可以看到它从 a 这个 Node 开始遍历,然后到 b,再到 c,最后重新回到 a。
另外还可以利用 start_node()、end_node()、nodes()、relationships() 方法来获取起始 Node、终止 Node、所有 Node 和 Relationship,例如:
print(w.start_node())
print(w.end_node())
print(w.nodes())
print(w.relationships())
>>> (alice:Person {name:"Alice"})
>>> (alice:Person {name:"Alice"})
>>> ((alice:Person {name:"Alice"}), (bob:Person {name:"Bob"}), (mike:Person {name:"Mike"}), (alice:Person {name:"Alice"}))
>>> ((alice)-[:KNOWS]->(bob), (bob)-[:LIKES]->(mike), (alice)-[:KNOWS]->(mike))
相关属性:
在 database 模块中包含了和 Neo4j 数据交互的 API,最重要的当属 Graph,它代表了 Neo4j 的图数据库
test_graph = Graph(
"http://localhost:7474",
username="neo4j",
password="xxxx"
)
test_graph
,就连接上了电脑中默认的图数据库,就可以进行查询了。
还可以利用 create() 方法传入 Subgraph 对象来将关系图添加到数据库中,实例如下:
from py2neo import Node, Relationship, Graph
a = Node('Person', name='Alice')
b = Node('Person', name='Bob')
r = Relationship(a, 'KNOWS', b)
s = a | b | r
graph = Graph(password='123456')
graph.create(s)
另外我们也可以单独添加单个 Node 或 Relationship,实例如下:
from py2neo import Graph, Node, Relationship
graph = Graph(password='123456')
a = Node('Person', name='Alice')
graph.create(a)
b = Node('Person', name='Bob')
ab = Relationship(a, 'KNOWS', b)
graph.create(ab)
查找是否存在节点 - exists(subgraph)
print(test_graph.exists(node3))
节点的度数
test_graph.degree(node3)
.
比较传统的方式:通过nodes的ID进行检索
graph = Graph()
# 其中的数字对应的是节点,ID
# 这个ID不按顺序来的,要注意
graph.nodes[1234]
graph.nodes.get(1234)
还有一种方式,match
的方式:
# .run/.data查询
test_graph.data("MATCH (a:Person {name:'You'}) RETURN a")
>>> [{'a': (c7d1cb9:Person {name:"You"})}]
list(test_graph.run("MATCH (a:Person {name:'You'}) RETURN a"))
>>>[('a': (c7d1cb9:Person {name:"You"}))]
test_graph.run("MATCH (a:Person {name:'You'}) RETURN a").data()
>>>[{'a': (c7d1cb9:Person {name:"You"})}]
# 查询关系
test_graph.run("MATCH (a:Person {name:'You'})-[b:FRIEND]->(c:Person {name:'Johan'} ) RETURN a,b,c")
graph.run(),之中填写的是查询语句。查询的结果也可以转换为dataframe的格式:
pd.DataFrame(test_graph.data("MATCH (a:Person {name:'Anna'}) RETURN a"))
a
0 {'name': 'Anna'}
1 {'name': 'Anna'}
2 {'name': 'Anna'}
3 {'name': 'Anna'}
其中需要注意的是,查询出来的结果是dict/list格式的,并不是graph型,于是不能进行后续查询。
查询出来的结果,可以标准化成一些表格的格式:
# graph查询
graph.run("MATCH (n:leafCategory) RETURN n LIMIT 25").data() # list型
graph.run("MATCH (n:leafCategory) RETURN n LIMIT 25").to_data_frame() # dataframe型
graph.run("MATCH (n:leafCategory) RETURN n LIMIT 25").to_table() # table
查找节点的个数:
# 节点个数
len(graph.nodes)
len(graph.nodes.match("leafCategory")) # 某类别的节点个数
通过find进行节点查询
另外的可以通过find
的方式进行查找:
# 查找全部
graph=test_graph.find(label='Person')
for node in graph:
print(node)
>>>(b54ad74:Person {age:18,name:"Johan"})
(b1d7b9d:Person {name:"Rajesh"})
(cf7fe65:Person {name:"Anna"})
(d780197:Person {name:"Julia"})
# 查找单节点
test_graph.find_one(label='Person',property_key='name',property_value='You')
>>> (c7d1cb9:Person {name:"You"})
此时返回的是可复用的图类型,就可以去衡量相关属性。
节点是否存在的判断
# 该节点是否存在
test_graph.exists(graph.nodes[1234])
py2neoV3有这个函数,py2neoV4没有该函数了,各位注意!!变成这个函数了:class py2neo.matching.NodeMatcher(graph)
参考v4 Handbook
NodeMatcher是为更好的查询节点,支持更多的查询条件,比graph更友好
selector = NodeMatcher(test_graph)
#selector = NodeSelector(test_graph)
list(selector.match("Person", name="Anna"))
list(selector.match("Person").where("_.name =~ 'J.*'", "1960 <= _.born < 1970"))
在这里我们用 NodeSelector 来筛选 age 为 21 的 Person Node,实例如下:
from py2neo import Graph, NodeMatcher
graph = Graph(password='123456')
selector = NodeMatcher(graph)
#selector = NodeSelector(graph)
persons = selector.match('Person', age=21)
print(list(persons))
另外也可以使用 where() 进行更复杂的查询,例如查找 name 是 A 开头的 Person Node,实例如下:
from py2neo import Graph, NodeSelector
graph = Graph(password='123456')
selector = NodeMatcher(graph)
persons = selector.match('Person').where('_.name =~ "A.*"')
print(list(persons))
另外也可以使用 order_by() 进行排序:
from py2neo import Graph, NodeSelector
graph = Graph(password='123456')
selector = NodeMatcher(graph)
persons = selector.match('Person').order_by('_.age')
print(list(persons))
还包括:
// 此时start_node为节点
for rel in test_graph.match(start_node=node3, rel_type="FRIEND"):
print(rel.end_node()["name"])
>>>Johan
Julia
Andrew
# match_one
test_graph.match_one(start_node=node3, rel_type="FRIEND")
>>> (c7d1cb9)-[:FRIEND]->(b54ad74)
push 跟set一样:更新、添加,push(subgraph) 更新节点、关系或子图
node = test_graph.find_one(label='Person')
node['age'] = 18
test_graph.push(node)
print(test_graph.find_one(label='Person'))
>>> (b54ad74:Person {age:18,name:"Johan"})
a = Node('Person', name='Alice')
a['age'] = 20
因为a集成了PropertyDict 类属性,所以可以像dict一样进行简单赋值或添加。
a.setdefault('location', '北京')
print(a)
>>> (alice:Person {age:20,location:"北京",name:"Alice"})
但如果赋值了 location 属性,则它会覆盖默认属性
data = {
'name': 'Amy',
'age': 21
}
a.update(data)
print(a)
delete(subgraph) 删除节点、关系或子图
delete_all() 删除数据库所有的节点和关系
from py2neo import Graph
graph = Graph(password='123456')
node = graph.find_one(label='Person')
relationship = graph.match_one(rel_type='KNOWS')
graph.delete(relationship)
graph.delete(node)
在删除 Node 时必须先删除其对应的 Relationship,否则无法删除 Node。
参考:https://cuiqingcai.com/4778.html
可以实现一个对象和 Node 的关联,例如:
from py2neo.ogm import GraphObject, Property, RelatedTo, RelatedFrom
class Movie(GraphObject):
__primarykey__ = 'title'
title = Property()
released = Property()
actors = RelatedFrom('Person', 'ACTED_IN')
directors = RelatedFrom('Person', 'DIRECTED')
producers = RelatedFrom('Person', 'PRODUCED')
class Person(GraphObject):
__primarykey__ = 'name'
name = Property()
born = Property()
acted_in = RelatedTo('Movie')
directed = RelatedTo('Movie')
produced = RelatedTo('Movie')
我们可以用它来结合 Graph 查询,例如:
from py2neo import Graph
from py2neo.ogm import GraphObject, Property
graph = Graph(password='123456')
class Person(GraphObject):
__primarykey__ = 'name'
name = Property()
age = Property()
location = Property()
person = Person.select(graph).where(age=21).first()
print(person)
print(person.name)
print(person.age)
>>>
>>>Alice
>>>21
这样我们就成功实现了对象和 Node 的映射。
我们可以用它动态改变 Node 的属性,例如修改某个 Node 的 age 属性,实例如下:
person = Person.select(graph).where(age=21).first()
print(person.__ogm__.node)
person.age = 22
print(person.__ogm__.node)
graph.push(person)
>>>(ccf5640:Person {age:21,location:"北京",name:"Mike"})
>>>(ccf5640:Person {age:22,location:"北京",name:"Mike"})
另外我们也可以通过映射关系进行 Relationship 的调整,例如通过 Relationship 添加一个关联 Node,实例如下:
from py2neo import Graph
from py2neo.ogm import GraphObject, Property, RelatedTo
graph = Graph(password='123456')
class Person(GraphObject):
__primarykey__ = 'name'
name = Property()
age = Property()
location = Property()
knows = RelatedTo('Person', 'KNOWS')
person = Person.select(graph).where(age=21).first()
print(list(person.knows))
new_person = Person()
new_person.name = 'Durant'
new_person.age = 28
person.knows.add(new_person)
print(list(person.knows))
运行结果:
[]
[, ]
这样我们就完成了 Node 和 Relationship 的添加,同时由于设置了 primarykey 为 name,所以不会重复添加。
但是注意此时数据库并没有更新,只是对象更新了,如果要更新到数据库中还需要调用 Graph 对象的 push() 或 pull() 方法,添加如下代码即可:
graph.push(person)
也可以通过 remove() 方法移除某个关联 Node,实例如下:
person = Person.select(graph).where(name='Alice').first()
target = Person.select(graph).where(name='Durant').first()
person.knows.remove(target)
graph.push(person)
graph.delete(target)
这里 target 是 name 为 Durant 的 Node,代码运行完毕后即可删除关联 Relationship 和删除 Node。
以上便是 OGM 的用法,查询修改非常方便,推荐使用此方法进行 Node 和 Relationship 的修改。
更多内容可以查看:http://py2neo.org/v3/ogm.html#module-py2neo.ogm。
matcher = RelationshipMatcher(g)
selector = NodeMatcher(g)
查询的方式为:
>>> from py2neo import Graph, NodeMatcher
>>> graph = Graph()
>>> matcher = NodeMatcher(graph)
>>> matcher.match("Person", name="Keanu Reeves").first()
(_224:Person {born:1964,name:"Keanu Reeves"})
边关系的查询class py2neo.matching.RelationshipMatch(graph, nodes=None, r_type=None, conditions=(), order_by=(), skip=None, limit=None)
:
matcher = RelationshipMatcher(graph)
result = matcher.match({node1,node2},'know')
print list(result)
这里RelationshipMatcher
比较特殊,match中可以加入多个node,如:{node1,node2}
这个命题有点绕,直接上代码。。
# 节点上传
for n in tqdm(range(len(node))):
param_dict = node.loc[n,:].to_dict()
group = param_dict['group']
param_dict.pop('group')
sql = 'CREATE(n:{} {})'.format(group,dict_to_str(param_dict))
g.run(sql)
# 边上传
for n in tqdm(range(len(relation))):
param_dict = relation.loc[n,:].to_dict()
a_group = param_dict['a_group']
a_id = param_dict['a_id']
b_group = param_dict['b_group']
b_id = param_dict['b_id']
rel = param_dict['relation']
[param_dict.pop(fr) for fr in fix_relation_col]
sql = 'match(p:{0}),(q:{1}) where p.id="{2}" and q.id="{3}" \
create (p)-[rel:{4} {5} ]->(q)'.format(a_group,b_group,a_id,b_id,rel,dict_to_str(param_dict))
#print(sql)
g.run(sql)
通过CREATE
的方式上传,另外一种是:
for n in tqdm(range(len(node))):
param_dict = node.loc[n,:].to_dict()
group = param_dict['group']
param_dict.pop('group')
# 构建节点
_node = Node(group)
for k,v in param_dict.items():
_node[k] = str(v)
# 上传
if len(self.selector.match(group, id=param_dict['id'])) == 0:
#print(1)
self.g.create(_node)
num += 1
param_dict = relation.loc[n,:].to_dict()
a_group = param_dict['a_group']
a_id = param_dict['a_id']
b_group = param_dict['b_group']
b_id = param_dict['b_id']
rel = param_dict['relation']
[param_dict.pop(fr) for fr in self.fix_relation_col]
# 创建点 + 边
node_a = list(self.selector.match(a_group, id = a_id))
node_b = list(self.selector.match(b_group, id = b_id))
if len(node_a) != 1:
#assert
print('node {}-{} num is :{}'.format(a_group,a_id,len(node_a)) )
if len(node_b) != 1:
#assert
print('node {}-{} num is :{}'.format(b_group,b_id,len(node_b)) )
node_a = node_a[0]
node_b = node_b[0]
r = Relationship(node_a, rel, node_b)
for k,v in param_dict.items():
r[k] = str(v)
# check
res = list(self.matcher.match({node_a,node_b},rel))
if len(res) == 0:
#if not g.exists(r):
self.g.create(r)
num += 1
这边笔者自己写了一个dataframe2neo4j的模板程序,后续会贴进博客之中。
[Neo4j系列四]Neo4j的python操作库py2neo之一
[Neo4j系列五]Neo4j的python操作库py2neo之二
[Neo4j系列六]Neo4j的python操作库py2neo之三
Neo4j简介及Py2Neo的用法