Neo4j入門(一)節(jié)點(diǎn)、關(guān)系的增刪改查
??本文作為筆者入門Neo4j的第一篇文章,后續(xù)將會(huì)更多地介紹Neo4j及知識(shí)圖譜相關(guān)的內(nèi)容。

??Neo4j是圖數(shù)據(jù)庫中的佼佼者,采用Java編寫,社區(qū)版已開源,商業(yè)版需收費(fèi)。Neo4j是一個(gè)高性能的NoSQL圖形數(shù)據(jù)庫(Graph Database),它將結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)在網(wǎng)絡(luò)上而不是表中。它是一個(gè)嵌入式的、基于磁盤的、具備完全的事務(wù)特性的Java持久化引擎,但是它將結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)在網(wǎng)絡(luò)(從數(shù)學(xué)角度叫做圖)上而不是表中。Neo4j也可以被看作是一個(gè)高性能的圖引擎,該引擎具有成熟數(shù)據(jù)庫的所有特性。Neo4j擁有十分酷炫的可視化界面,這是一般圖數(shù)據(jù)庫所不具備的。
??本文將通過一個(gè)簡單的例子來介紹Neo4j中的基本操作:對(duì)節(jié)點(diǎn)和邊實(shí)現(xiàn)增刪改查。我們想要實(shí)現(xiàn)的圖(作為例子,簡單易懂)如下:

??在上述例子中,共有兩類節(jié)點(diǎn):City和Company,一類關(guān)系:(Company) -[belongTo]-> (City)。
CQL
??Neo4j 的 CQL 是非常重要的命令,類似于 SQL 語句,具體的用法可以參考:https://www.w3cschool.cn/neo4j/neo4j_cql_introduction.html。我們使用CQL實(shí)現(xiàn)節(jié)點(diǎn)、關(guān)系的增刪改查。
ADD
??City的節(jié)點(diǎn)可以通過使用如下命令使用:
create (city: City{name: "上海市", area: "6340.5平方千米", population: "2487.09萬人", alias: ["滬", "申"]});
create (city: City{name: "北京市", area: "16410平方千米", population: "2189.31萬人", alias: ["京", "帝都"]});
create (city: City{name: "深圳市", area: "1997.49平方千米", population: "1756萬人", alias: ["深", "鵬城"]});
create (city: City{name: "杭州市", area: "16850平方千米", population: "1193萬人", alias: ["杭"]});
在上述語句中,create表示新建,小括號(hào)內(nèi)是節(jié)點(diǎn)信息,節(jié)點(diǎn)的類型(label)是City,city是其別名,花括號(hào)內(nèi)是該節(jié)點(diǎn)的屬性,共有name、area、population、alias四個(gè)屬性。注意:Neo4j支持列表這個(gè)數(shù)據(jù)類型,但不支持時(shí)間日期這個(gè)數(shù)據(jù)類型。
??Company的節(jié)點(diǎn)可以通過使用如下命令使用:
create (company: Company{name: "阿里"});
create (company: Company{name: "網(wǎng)易"});
create (company: Company{name: "百度"});
create (company: Company{name: "字節(jié)跳動(dòng)"});
create (company: Company{name: "新浪"});
create (company: Company{name: "拼多多"});
create (company: Company{name: "B站"});
create (company: Company{name: "小紅書"});
create (company: Company{name: "華為"});
create (company: Company{name: "騰訊"});
create (company: Company{name: "招商銀行"});
??創(chuàng)建關(guān)系的命令如下:
match (city: City{name: "杭州市"}), (company: Company{name: "阿里"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
match (city: City{name: "杭州市"}), (company: Company{name: "網(wǎng)易"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
match (city: City{name: "上海市"}), (company: Company{name: "拼多多"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
match (city: City{name: "上海市"}), (company: Company{name: "B站"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
match (city: City{name: "上海市"}), (company: Company{name: "小紅書"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
match (city: City{name: "北京市"}), (company: Company{name: "新浪"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
match (city: City{name: "北京市"}), (company: Company{name: "百度"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
match (city: City{name: "北京市"}), (company: Company{name: "字節(jié)跳動(dòng)"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
match (city: City{name: "深圳市"}), (company: Company{name: "華為"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
match (city: City{name: "深圳市"}), (company: Company{name: "騰訊"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
match (city: City{name: "深圳市"}), (company: Company{name: "招商銀行"}) create (company) -[r:belongTo{name: "所在城市"}]-> (city);
事實(shí)上,我們還可以用如下命令來實(shí)現(xiàn)關(guān)系的新建:
create (company: Company{name: "阿里"}) -[r:belongTo{name: "所在城市"}]-> (city:City{name: "杭州市"});
update
??如果需要更新某個(gè)節(jié)點(diǎn)的屬性,命令如下(比如將上海市節(jié)點(diǎn)的簡稱新增“魔都”這個(gè)名字):
MATCH (city:City)
WHERE city.name="上海市"
SET city.alias=["滬", "申", "魔都"]
RETURN city;

上述命令也涉及到了查詢(select),只是CQL的查詢與SQL接近但又不相同。當(dāng)然,我們也可以修改某條關(guān)系。
delete
??CQL的刪除命令有delete和remove,兩者的區(qū)別為:
delete命令為刪除節(jié)點(diǎn)、刪除節(jié)點(diǎn)及相關(guān)節(jié)點(diǎn)和關(guān)系;
remove命令為刪除節(jié)點(diǎn)或關(guān)系的標(biāo)簽、刪除節(jié)點(diǎn)或關(guān)系的屬性
我們以刪除上海市節(jié)點(diǎn)的alias屬性為例,命令如下:
MATCH (city: City{name: "上海市"})
REMOVE city.alias
RETURN city;

注意,當(dāng)我們?cè)趧h除某個(gè)節(jié)點(diǎn)的時(shí)候,需要確定該節(jié)點(diǎn)無其他節(jié)點(diǎn)無關(guān)系相連,即該節(jié)點(diǎn)為孤立節(jié)點(diǎn),否則,就會(huì)報(bào)錯(cuò)。比如,我們想刪除上海市這個(gè)節(jié)點(diǎn),命令如下:
MATCH (city: City{name: "上海市"})
DELETE city;
報(bào)錯(cuò)如下:

select
??CQL的查詢語句是靈活、功能強(qiáng)大的。我們僅介紹簡單的幾個(gè)命令。
查詢所有的節(jié)點(diǎn):
match (n) return (n);
查詢所有節(jié)點(diǎn)的數(shù)量
match (n) return count(n);
查詢所有的Company節(jié)點(diǎn)
match (n:Company) return (n);
查詢名字為北京市的City節(jié)點(diǎn)
match (n:City{name: "北京市"}) return (n);
查詢招商銀行與深圳市的關(guān)系
match (a:Company{name: "招商銀行"}) -[r]-> (b:City{name: "深圳市"}) return type(r);
查詢?cè)搱D譜中杭州市下面的Company
match (a:Company) -[r:belongTo]-> (b:City{name: "杭州市"}) return (a);
CQL還支持更高級(jí)的查詢,如最短路徑查詢等。
py2neo
??py2neo是用來對(duì)接 Neo4j 的 Python 庫,官方文檔地址為http://py2neo.org/v3/index.html,
GitHub地址為https://github.com/technige/py2neo。
??下面將介紹如何使用py2neo實(shí)現(xiàn)對(duì)Neo4j的簡單操作。
neo4j的連接
??py2neo連接Neo4j需要Neo4j的服務(wù)網(wǎng)址,用戶名和密碼,示例連接Python代碼如下:
# -*- coding: utf-8 -*-
from py2neo import Graph
url = "http://localhost:7474"
username = "neo4j"
password = "password"
graph = Graph(url, auth=(username, password))
print("neo4j info: {}".format(str(graph)))
輸出結(jié)果如下:
neo4j info: Graph('http://neo4j@localhost:7474')
ADD
??我們來創(chuàng)建4個(gè)City節(jié)點(diǎn)和11個(gè)Company節(jié)點(diǎn),代碼如下:
from py2neo import Node, Relationship, Subgraph
city_list = [{"name": "上海市", "area": "6340.5平方千米", "population": "2487.09萬人", "alias": ["滬", "申"]},
{"name": "北京市", "area": "16410平方千米", "population": "2189.31萬人", "alias": ["京", "帝都"]},
{"name": "深圳市", "area": "1997.49平方千米", "population": "1756萬人", "alias": ["深", "鵬城"]},
{"name": "杭州市", "area": "16850平方千米", "population": "1193萬人", "alias": ["杭"]}]
company_list = [{"name": "阿里"},
{"name": "網(wǎng)易"},
{"name": "百度"},
{"name": "新浪"},
{"name": "字節(jié)跳動(dòng)"},
{"name": "小紅書"},
{"name": "B站"},
{"name": "拼多多"},
{"name": "華為"},
{"name": "招商銀行"},
{"name": "騰訊"}
]
for city in city_list:
node = Node("City", **city)
graph.create(node)
for company in company_list:
node = Node("Company", **company)
graph.create(node)
或者可以通過子圖(Subgraph)來創(chuàng)建,這樣創(chuàng)建效率更高,使用子圖的示例代碼如下:
from py2neo import Node, Relationship, Subgraph
city_list = [{"name": "上海市", "area": "6340.5平方千米", "population": "2487.09萬人", "alias": ["滬", "申"]},
{"name": "北京市", "area": "16410平方千米", "population": "2189.31萬人", "alias": ["京", "帝都"]},
{"name": "深圳市", "area": "1997.49平方千米", "population": "1756萬人", "alias": ["深", "鵬城"]},
{"name": "杭州市", "area": "16850平方千米", "population": "1193萬人", "alias": ["杭"]}]
company_list = [{"name": "阿里"},
{"name": "網(wǎng)易"},
{"name": "百度"},
{"name": "新浪"},
{"name": "字節(jié)跳動(dòng)"},
{"name": "小紅書"},
{"name": "B站"},
{"name": "拼多多"},
{"name": "華為"},
{"name": "招商銀行"},
{"name": "騰訊"}
]
node_list = []
for city in city_list:
node_list.append(Node("City", **city))
for company in company_list:
node_list.append(Node("Company", **company))
new_sub = Subgraph(node_list)
graph.create(new_sub)
??創(chuàng)建City節(jié)點(diǎn)與Company節(jié)點(diǎn)之間的關(guān)系的示例代碼如下:
# 創(chuàng)建關(guān)系
from py2neo import NodeMatcher
rel_dict = {"上海市": ["小紅書", "B站", "拼多多"],
"北京市": ["百度", "新浪", "字節(jié)跳動(dòng)"],
"深圳市": ["華為", "招商銀行", "騰訊"],
"杭州市": ["阿里", "網(wǎng)易"]}
node_matcher = NodeMatcher(graph=graph)
for key, vals in rel_dict.items():
for val in vals:
start_node = node_matcher.match("Company", name=val).first()
end_end = node_matcher.match("City", name=key).first()
rel = Relationship(start_node, "belongTo", end_end, name="所在城市")
graph.create(rel)
這樣創(chuàng)建的圖譜就是一開始演示的例子。當(dāng)然,我們也可以用Subgraph來創(chuàng)建,效率會(huì)更高。
UPDATE
??如果需要更新某個(gè)節(jié)點(diǎn)的屬性,示例Python代碼如下(比如將上海市節(jié)點(diǎn)的簡稱新增“魔都”這個(gè)名字):
# before update
node_matcher = NodeMatcher(graph=graph)
node = node_matcher.match("City", name="上海市").first()
print(node.__repr__())
# update node
node["alias"] = ['滬', '申', '魔都']
graph.push(node)
# after update
node = node_matcher.match("City", name="上海市").first()
print(node.__repr__())
輸出結(jié)果如下:
Node('City', alias=['滬', '申'], area='6340.5平方千米', name='上海市', population='2487.09萬人')
Node('City', alias=['滬', '申', '魔都'], area='6340.5平方千米', name='上海市', population='2487.09萬人')
當(dāng)然,我們也可以更新關(guān)系。
DELETE
??我們以刪除上海市節(jié)點(diǎn)的alias屬性為例,示例Python代碼如下:
# before delete
node_matcher = NodeMatcher(graph=graph)
node = node_matcher.match("City", name="上海市").first()
print(node.__repr__())
# delete node property
del node["alias"]
graph.push(node)
# after update
node = node_matcher.match("City", name="上海市").first()
print(node.__repr__())
輸出結(jié)果如下:
Node('City', alias=['滬', '申', '魔都'], area='6340.5平方千米', name='上海市', population='2487.09萬人')
Node('City', area='6340.5平方千米', name='上海市', population='2487.09萬人')
當(dāng)然,我們也可以刪除節(jié)點(diǎn)。
SELECT
??py2neo也能夠很好地支持查詢語句,示例代碼如下:
查詢所有的節(jié)點(diǎn):
graph.nodes.match().all()
查詢所有節(jié)點(diǎn)的數(shù)量
len(graph.nodes.match().all())
查詢所有的Company節(jié)點(diǎn)
graph.nodes.match("Company").all()
查詢名字為北京市的City節(jié)點(diǎn)
graph.nodes.match("City", name="北京市").all()
查詢招商銀行與深圳市的關(guān)系
node_matcher = NodeMatcher(graph=graph)
end_node = node_matcher.match("City", name="深圳市").first()
start_node = node_matcher.match("Company", name="招商銀行").first()
print(graph.match([start_node, end_node]).first())
查詢?cè)搱D譜中杭州市下面的Company
from py2neo import RelationshipMatcher
node_matcher = NodeMatcher(graph=graph)
node = node_matcher.match("City", name="杭州市").first()
rel_matcher = RelationshipMatcher(graph=graph)
find_rels = rel_matcher.match([None, node], r_type="belongTo").all()
print([_.start_node for _ in find_rels if _.start_node.has_label("Company")])
py2neo還支持CQL語句,我們以最后的查詢語句為例,示例代碼如下:
cql = 'match (a:Company) -[r:belongTo]-> (b:City{name: "杭州市"}) return (a); '
print(graph.run(cql).data())
輸出結(jié)果如下:
[{'a': Node('Company', name='網(wǎng)易')}, {'a': Node('Company', name='阿里')}]
總結(jié)
??綜上,我們可以了解到,neo4j的CQL以及py2neo各有各的好處,CQL在某些查詢場合比較便利,而py2neo可以方便我們使用Python進(jìn)行對(duì)Neo4j的操作,有些操作實(shí)現(xiàn)起來也很方便。
??本文簡單介紹了Neo4j節(jié)點(diǎn)、關(guān)系的增刪改查,以及CQL、py2neo的使用方法。后續(xù)會(huì)持續(xù)更新Neo4j相關(guān)內(nèi)容,歡迎大家關(guān)注~
??2021.7.13-7.14深夜,最近上海酷暑~
