1. 程式人生 > >neo4j︱與python結合的py2neo使用教程(四)

neo4j︱與python結合的py2neo使用教程(四)

圖資料庫常規的有:neo4j(支援超多語言)、JanusGraph/Titan(分散式)、Orientdb,google也開源了圖資料庫Cayley(Go語言構成)、PostgreSQL儲存RDF格式資料。

一、建立與基本使用、屬性查詢

1.1 建立節點與關係

舉個簡單的例子:

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。
其中:

  • class Node(*labels, **properties)
  • class Relationship(start_node, type, end_node, **properties)

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)

其中包含的節點屬性有:

  • hash(node) 返回node的ID的雜湊值
  • node[key] 返回node的屬性值,沒有此屬性就返回None
  • node[key] = value 設定node的屬性值
  • del node[key] 刪除屬性值,如果不存在此屬性報KeyError
  • len(node) 返回node屬性的數量
  • dict(node) 返回node所有的屬性
  • walk(node) 返回一個生成器且只包含一個node
  • labels() 返回node的標籤的集合
  • has_label(label) node是否有這個標籤
  • add_label(label) 給node新增標籤
  • remove_label(label) 刪除node的標籤
  • clear_labels() 清楚node的所有標籤
  • update_labels(labels) 新增多個標籤,注labels為可迭代的

其中連線的屬性有:

  • hash(relationship) 返回一個關係的hash值
  • relationship[key] 返回關係的屬性值
  • relationship[key] = value 設定關係的屬性值
  • del relationship[key] 刪除關係的屬性值
  • len(relationship) 返回關係的屬性值數目
  • dict(relationship) 以字典的形式返回關係的所有屬性
  • walk(relationship) 返回一個生成器包含起始node、關係本身、終止node
  • type() 返回關係type

1.2 子圖Subgraphs

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

其中子圖擁有的屬性內容:

  • subgraph | other | … 子圖的並
  • subgraph & other & … 子圖的交
  • subgraph - other - … 子圖的差
  • subgraph ^ other ^ … 子圖對稱差
  • subgraph.keys() 返回子圖節點和關係所有屬性的集合
  • subgraph.labels() 返回節點label的集合
  • subgraph.nodes() 返回所有節點的集合
  • subgraph.relationships() 返回所有關係的集合
  • subgraph.types() 返回所有關係的type的集合
  • order(subgraph) 返回子圖節點的數目
  • size(subgraph) 返回子圖關係的數目

1.3 Walkable Types

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))

相關屬性:

  • walk(walkable) 轉為一個生成器包含節點和關係
  • start_node() 返回walk()的起始節點
  • end_node() 返回walk()的最後節點
  • nodes() 返回walk()所有節點的元組
  • relationships() 返回walk()所有關係的元組

1.4 連線已有圖資料庫 - .Graph()

在 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)

1.5 其他應用

查詢是否存在節點 - exists(subgraph)

print(test_graph.exists(node3))

節點的度數

test_graph.degree(node3)

.

二、查詢方式

2.1 結果查詢-.run/.data/.match

比較傳統的方式:通過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

2.2 查詢節點-.find/.find_one

查詢節點的個數:

# 節點個數
len(graph.nodes)
len(graph.nodes.match("leafCategory")) # 某類別的節點個數

通過find進行節點查詢
另外的可以通過find的方式進行查詢:
- .find,查詢全部,需要傳入的不定引數label、property_key、property_value、limit,返回符合篩選條件節點的生成器
- .find_one,只查詢單節點,需要傳入的不定引數label、property_key、property_value,返回符合篩選條件一個節點,即使多個都滿足,也會返回唯一節點

# 查詢全部
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])

2.3 更靈活的查詢 - NodeMatcher

py2neoV3有這個函式,py2neoV4沒有該函數了,各位注意!!變成這個函數了:class py2neo.matching.NodeMatcher(graph)參考v4 Handbook

NodeMatcher是為更好的查詢節點,支援更多的查詢條件,比graph更友好

selector = NodeMatcher(test_graph)
#selector = NodeSelector(test_graph)
list(selector.select("Person", name="Anna"))
list(selector.select("Person").where("_.name =~ 'J.*'", "1960 <= _.born < 1970"))

在這裡我們用 NodeSelector 來篩選 age 為 21 的 Person Node,例項如下:

from py2neo import Graph, NodeSelector

graph = Graph(password='123456')
selector = NodeMatcher(graph)
#selector = NodeSelector(graph)
persons = selector.select('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.select('Person').where('_.name =~ "A.*"')
print(list(persons))

另外也可以使用 order_by() 進行排序:

from py2neo import Graph, NodeSelector

graph = Graph(password='123456')
selector = NodeMatcher(graph)
persons = selector.select('Person').order_by('_.age')
print(list(persons))

還包括:

  • first()返回單個節點
  • limit(amount)返回底部節點的限值條數
  • skip(amount)返回頂部節點的限值條數
  • order_by(*fields)排序
  • where(*conditions, **properties)篩選條件

2.3 match() 或 match_one() 查詢Relationship

  • .match 匹配關係
  • .match_one,匹配並返回所有滿足條件的一條關係
// 此時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)

2.4 類似set的重設 - push、setdefault、update

push 跟set一樣:更新、新增,push(subgraph) 更新節點、關係或子圖

  • push
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"})
  • PropertyDict 類屬性
a = Node('Person', name='Alice')
a['age'] = 20

因為a集成了PropertyDict 類屬性,所以可以像dict一樣進行簡單賦值或新增。

  • setdefault() 方法
a.setdefault('location', '北京')
print(a)
>>> (alice:Person {age:20,location:"北京",name:"Alice"})

但如果賦值了 location 屬性,則它會覆蓋預設屬性

  • update() 方法對屬性批量更新
data = {
    'name': 'Amy',
    'age': 21
}
a.update(data)
print(a)

2.5 刪除 - .delete()/.delete_all()

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。

三、OGM - Object Graph Mapping

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)

>>><Person name='Alice'>
>>>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))

執行結果:

[<Person name='Bob'>]
[<Person name='Bob'>, <Person name='Durant'>]

這樣我們就完成了 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 的修改。

同時參考文獻: