neo4j目前是图数据库的主流,neo4j的Cypher语法简单直观,但是不便于流程化。如果习惯在python环境下处理数据,那么还是要用到python的neo4j库,即py2neo.
py2neo本身并不复杂,但要先适应它的思考模式。另一个问题是py2neo文档的示例较少,而且不同版本的py2neo挺不相同,容易弄混。这里要讲的是目前的v4版本。
连接数据库和图
from py2neo import * # *中常用的是Node,Relationship,Graph
graph = Graph(url,username='name',password='pw')
2.查看数据库的基本属性
graph.schema.node_labels # 查看图结构中节点标签的类别,返回结果是一个frozenset
graph.schema.relationship_types # 查看图结构中关系的类型
3.数据操作
数据操作分为两种方式,一种是直接传入cypher语句,这里我们称之为cypher外壳;另一种是采用py2neo自己的数据结构和编写方式,我们称为py2neo方法。
在讲这两种方式之前,先提一下通用的pull()、push()等操作。
因为常需要从服务器端取数据,同时编写程序时本地也会产生Node,Relationship等数据,因此需要二者的版本协同。常规的处理方法如下:
graph = Graph()
tx = graph.begin() # 开始一个Transaction
node_1 = Node("Person",name="Peter")
tx.create(node_1)
tx.push(node_1) # push到服务器
#or
tx.commit() # 将以上更改一次提交
3.1 cypher外壳
这种方法的基本用法是graph.run(‘a cypher expression’),如
result =graph.run('match (p:Paper) return p.title,p.author limit 20')
适合更熟悉Cypher语法的同学使用,需要注意的是,运行graph.run()返回的结果是一个cursor对象。cursor相当于结果的一个游标(a cursor is a navigator for a stream of records).可以通过循环调用。
while cursor.forward():
print(cursor.current['name'])
# or
for record in cursor:
print(record["name"])
此外graph.run()的结果可以通过数据形式呈现。数据基本格式是list of dictionary,可以按需求转换为其它格式:
graph.run().data() # a list of dictionary
graph.run().to_data_frame() # pd.DataFrame
graph.run().to_ndarray() # numpy.ndarray
graph.run().to_subgraph()
graph.run().to_table()
3.2 py2neo方法
py2neo方法即前面的graph类中的函数。neo4j的主要功能是图数据的存储和查询,相应的py2neo方法也可以从这两方面来考察。
(1)存储
除了前面的创建节点,还有创建关系以及子图。
a = Node("Person",name="Alice")
b = Node("Person",name="Bob")
r = Relationship(a,"KNOWS",b)
s = a|b|r
graph = Graph()
graph.create(s)
创建节点可以采用merge方法,需要注意的是py2neo和cypher中的merge方法有所不同。
在cypher中,MERGE方式是当你创建一个实体时,程序会检测是否已有这个实体存在,检测的方法是进行label和property的匹配。如果已存在则不创建。 py2neo方法中的merge则是,同样进行匹配,如果匹配上则用当前实体覆盖数据库中的已有实体。 这里的主要区别在于,匹配时一般只会用到关键的少数property,根据某个property去决定是否覆盖时,其他property可能是不相等的。
因此cypher是用数据库实体覆盖新创建的,py2neo是用新的覆盖旧的。 考虑到创建时属性可能比较少,因此在py2neo中慎用merge,可以先做存在判断,然后再用create语句.
具体的,merge语句的使用方式为:
graph.merge(node_1,"Person","name") # 根据name属性对person结点进行merge
相应的cypher语句中的merge为:
match (c:course)
merge (t:teacher {name:c.teacher})
return c.name,c.teacher
(2)查询
py2neo提供了专门的查询模块,即NodeMatcher和RelationshipMatcher,使用格式为
matcher_1 = NodeMatcher(graph)
matcher_2 = RelationshipMatcher(graph)
node = matcher_1.match("Paper",ID='09076').where(year=2017) # 匹配指定ID和year的Paper结点
relation = matcher_2.matcher(r_type="Cited").limit(50)
注意使用这两个模块后返回的结果仍然是NodeMatcher对象或RelationshipMatcher对象,要将其转换为实体,可以采用first()函数,或者变换成列表。
node_1 = matcher_1.match("Paper",ID='09076').first() # 取第一个匹配到的节点
result_1 = list(node) # 转换为列表
result_2 = list(result)
此外,node和relationship数据类型也自带匹配属性,如
graph.node.matcher("Person").first()
但是这样匹配的效率似乎比较低,运行更慢。
以上py2neo的匹配,其缺点在于匹配被人为分割成节点和关系的匹配,使用起来不灵活,因为节点和关系的匹配常常是紧密联系的。相比之下,cypher的原生匹配就很灵活,没有限定类型。
match g = (p1:Person) -[r:]->(p2:Paper)
where p2.year>2008
return g,p1.name,r.type,p2.title
3.3 py2neo.ogm
py2neo中的ogm模块是Object-Graph Mapping的简写,这里将它和普通py2neo方法分开单独讲,ogm是基于Graphobject的。
一个Graphobject的实例可以包含节点、标签或相关对象等内容。
class Movie(GraphObject):
__primarylabel__ = 'Movie'
__primarykey__ = 'title'
title = Property() # 影片名
tag_line = Property('tagline')
release = Property() # 发行时间
restricted = Lable() # 是否限制级
actors = RelatedFrom("Person","ACTED_IN")
directors = RelatedFrom("Person","DIRECTED")
producers = RelatedFrom("Person","PRODUCED")
class Person(GraphObject):
__primarykey__ = "name"
name = Property()
acted_in = RelatedTo(Movie)
Property是属性,通过语句如release = Property()指定release为属性,接着就可以赋值
M = Movie()
M.release = 1995
Label是标签,用法同Property,但Label是binary变量。
RelationTo和RelatedFrom指定了关系管理的实体和关系的方向。
graphObject对象自身也可以进行查询操作,如Person.match(graph,“Alice”).first()
如前文所说,效率可能没有NodeMatcher方法高。
以上就是笔者整理的py2neo基本使用方法,作为一个小工具,py2neo内容本身不多,但是限于篇幅,还有一些有用的内容没有涉及到。例如为实体添加特定约束等,有机会再写。