图数据库概述¶
- 图数据库是一种用于存储和处理复杂网络数据的数据库系统。
- 使用图结构进行语义查询,节点、边和属性代表存储的数据
- 使用属性图模型:
- 实体(节点)可以持有任意数量的属性(键值对),并且可以用标签(类型)标记,代表它们在您的领域中的不同角色
- 关系提供两个节点实体之间的有向、命名连接。关系总是有方向的,有类型,有起始节点和结束节点
- 适合半结构化和高度连接的数据
- 需要一种新的查询语言
图数据库与关系数据库的比较¶
- 关系数据库
- 以行和列的表格形式存储数据,遵循严格的关系模式(Schema)。数据通过外键(Foreign Key)连接在一起。适合存储结构化数据,遵循关系代数的操作。
- 使用结构化查询语言(SQL)进行数据的查询和管理。查询的复杂度随着数据关系的增加而增加,尤其是涉及多表联接的情况。
- 在数据量增加时,通常通过垂直扩展(Vertical Scaling)方式,即通过提升服务器性能来支持更大的数据存储和处理。但当数据规模增大到一定程度时,性能会受到限制。
- 适合于数据关系较简单、需要事务性处理(如银行系统、库存管理)的应用场景。对事务处理和一致性要求高的系统更适合使用关系数据库。
图数据库与关系数据库的比较¶
- 图数据库
- 使用节点(Nodes)、边(Edges)和属性(Properties)来表示和存储数据。节点代表实体,边表示实体之间的关系,适合用来表示高度连接的数据。图数据库中,数据的结构可以随时扩展,不需要预定义固定的模式。
- 使用图查询语言,如Cypher(Neo4j)。图数据库非常适合快速执行多跳(multi-hop)查询,不需进行多表联接,查询效率更高。
- 更适合水平扩展,能够方便地将数据分布到多个节点上,扩展性更强。因此在处理大型数据集,尤其是数据之间存在复杂关系时,图数据库更有优势。
- 广泛用于社交网络、推荐系统、欺诈检测、供应链管理等场景,这些领域的数据之间具有复杂的关系。图数据库在图数据分析、路径发现和社交网络关系分析等方面具有独特优势。
Neo4j图数据库¶
- Neo4j 主要通过节点(Nodes)、关系(Relationships)和属性(Properties)来存储和管理数据。
- Neo4j 使用 Cypher 语言进行数据的查询、更新和管理。Cypher 类似 SQL,但专为图数据库设计,具有简单、易读的图形化查询语法。
- Neo4j 支持 ACID(Atomicity、Consistency、Isolation、Durability)特性,确保数据的可靠性和一致性。
- Neo4j 支持水平和垂直扩展,虽然它原本是一个集中式数据库,但 Neo4j Enterprise Edition 允许在多节点集群上部署,支持更大规模的数据集。
Cypher查询语言¶
- Cypher 是 Neo4j 专门设计的一种图数据库查询语言,用于查询、操作和管理图数据。它类似 SQL,但更适合图数据的查询操作,允许用户通过直观的语法定义复杂的关系查询。
- 图形化的查询语法
- Cypher 语句的语法设计直接反映了图数据库的结构:节点用圆括号表示 (),关系用方括号表示 []。
- Cypher 的设计目标之一是“人类可读性”,它用简洁的语法描述复杂的关系。
- Cypher 操作符合 ACID 特性,确保查询和数据操作具有原子性、一致性、隔离性和持久性。
- 开发者可以在不同平台上轻松调用 Cypher 语句,特别适合用于图数据库的集成开发和应用扩展。
- Cypher查询的结构
- 节点被括号包围,看起来像圆圈,例如
(a)
- 关系基本上是两个节点之间的箭头
-
,->
,在箭头内方括号[]
中放置额外的信息
- 节点被括号包围,看起来像圆圈,例如
Neo4j资源¶
Neo4j浏览器¶
- 打开URL http://localhost:7474(将“localhost”替换为您的服务器名称,将7474替换为neo4j.conf中设置的端口名称)
- 也可以利用bolt打开(可以从Neo4J desktop打开)
- 输入用户名/密码(如果没有设置,Neo4j浏览器将提示您选择用户名和密码)
- 通过输入Cypher查询并观察结果开始使用Neo4j
- 将常用查询保存到收藏夹
Neo4J¶
- https://neo4j.com/docs/getting-started/introduction/
- 在 Neo4j 中,信息以节点、关系和属性的形式组织。
- 节点是图中的实体。
- 节点可以标记标签,以表示它们在域中的不同角色(例如,Person)。
- 节点可以拥有任意数量的键值对或属性(例如,name)。
- 关系提供了两个节点实体之间的有方向、命名的连接(例如,Person LOVES Person)。
- 关系始终具有方向、类型、起始节点和结束节点,并且可以像节点一样拥有属性。
- 节点可以具有任意数量或类型的关系,而不会影响性能。
- 节点是图中的实体。
Neo4j Python库¶
- Neo4j 提供了 Python 库,用于在 Python 应用程序中连接和操作 Neo4j 数据库。这个库封装了与 Neo4j 数据库的交互,使开发者能够在 Python 环境中使用 Cypher 查询来管理和分析图数据。
- 安装
- pip install neo4j
- 步骤:
- 连接数据库:使用 Neo4j 驱动程序的核心是 GraphDatabase 类,通过 GraphDatabase.driver 方法创建数据库连接。连接 Neo4j 时需要提供数据库的 URI、用户名和密码。
- Session 对象用于管理会话,可以执行 Cypher 查询。
- 使用 session.run() 可以执行 Cypher 查询。
- 操作完成后需要关闭驱动程序连接,以确保资源被正确释放。
In [7]:
# 连接数据库并建立会话对象
import warnings
warnings.filterwarnings('ignore')
from neo4j import GraphDatabase
# URI examples: "neo4j://localhost", "neo4j+s://xxx.databases.neo4j.io"
URI = "neo4j://localhost"
AUTH = ("neo4j", "12345678")
driver = GraphDatabase.driver(URI, auth=AUTH)
session = driver.session(database="example")
In [14]:
session.run("MATCH (n) DETACH DELETE n")
def print_nodes():
results = session.run("MATCH (n) RETURN *")
for record in results:
print(record.data())
def print_relationships():
results = session.run("MATCH (n1)-[r]->(n2) RETURN n1, r, n2")
for record in results:
print(record.data())
CREATE语句¶
CREATE
子句允许您创建一个或多个节点。每个节点都可以分配标签和属性。您可以将每个节点绑定到一个变量,以便稍后在查询中引用该变量。多个标签用冒号分隔。
In [15]:
session.run("CREATE (charlie:Person:Actor {name: 'Charlie Sheen'}), (oliver:Person:Director {name: 'Oliver Stone'})")
Out[15]:
<neo4j._sync.work.result.Result at 0x2acff056d90>
- 关系也可以使用CREATE语句创建。与节点不同,关系始终只需要一种关系类型和一个方向。与节点类似,关系可以分配属性和关系类型,并绑定到变量。
In [16]:
session.run("""CREATE (charlie:Person:Actor {name: 'Charlie Sheen'})-[:ACTED_IN {role: 'Bud Fox'}]
->(wallStreet:Movie {title: 'Wall Street'})<-
[:DIRECTED]-(oliver:Person:Director {name: 'Oliver Stone'})""")
Out[16]:
<neo4j._sync.work.result.Result at 0x2ac82764690>
MATCH语句¶
- MATCH子句可以指定模式中的节点、关系和属性,从而允许遍历图形的查询来检索相关数据。
In [17]:
# 查找图中所有节点
results = session.run("MATCH (n) RETURN n")
for record in results:
print(record.data())
{'n': {'title': 'Wall Street'}} {'n': {'name': 'Oliver Stone'}} {'n': {'name': 'Charlie Sheen'}} {'n': {'name': 'Oliver Stone'}} {'n': {'name': 'Charlie Sheen'}}
In [18]:
# 查找具有特定标签的节点
results = session.run("MATCH (movie:Movie) RETURN movie")
results.data()
Out[18]:
[{'movie': {'title': 'Wall Street'}}]
- 上例在指定节点之间创建了一条路径。请注意,这些新创建的节点和关系与图中先前的内容无关。要将它们连接到现有数据,请将所需的节点和关系绑定到变量。然后,这些变量可以传递给查询中针对图中现有元素的后续子句。
In [19]:
session.run("MATCH (n) DETACH DELETE n")
session.run("CREATE (charlie:Person:Actor {name: 'Charlie Sheen'}), (oliver:Person:Director {name: 'Oliver Stone'})")
session.run("""MATCH (charlie:Person {name: 'Charlie Sheen'}), (oliver:Person {name: 'Oliver Stone'})
CREATE (charlie)-[:ACTED_IN {role: 'Bud Fox'}]->(wallStreet:Movie {title: 'Wall Street'})<-[:DIRECTED]-(oliver)""")
Out[19]:
<neo4j._sync.work.result.Result at 0x2ac82744090>
DELETE语句¶
- 该DELETE子句用于删除节点、关系或路径。
In [20]:
# 删除所有节点
session.run("MATCH (n) DETACH DELETE n")
Out[20]:
<neo4j._sync.work.result.Result at 0x2ac827a8990>
In [21]:
# 先创建一些节点和边
session.run("""CREATE
(keanu:Person {name: 'Keanu Reever'}),
(laurence:Person {name: 'Laurence Fishburne'}),
(carrie:Person {name: 'Carrie-Anne Moss'}),
(tom:Person {name: 'Tom Hanks'}),
(theMatrix:Movie {title: 'The Matrix'}),
(keanu)-[:ACTED_IN]->(theMatrix),
(laurence)-[:ACTED_IN]->(theMatrix),
(carrie)-[:ACTED_IN]->(theMatrix)""")
Out[21]:
<neo4j._sync.work.result.Result at 0x2acff0eec90>
In [22]:
# 删除单个节点
results = session.run("""MATCH (n) RETURN n""")
for record in results:
print(record.data())
session.run("""MATCH (n:Person {name: 'Tom Hanks'}) DELETE n""")
print("********************************")
results = session.run("""MATCH (n) RETURN n""")
for record in results:
print(record.data())
{'n': {'title': 'The Matrix'}} {'n': {'name': 'Keanu Reever'}} {'n': {'name': 'Laurence Fishburne'}} {'n': {'name': 'Carrie-Anne Moss'}} {'n': {'name': 'Tom Hanks'}} ******************************** {'n': {'title': 'The Matrix'}} {'n': {'name': 'Keanu Reever'}} {'n': {'name': 'Laurence Fishburne'}} {'n': {'name': 'Carrie-Anne Moss'}}
In [23]:
# 可以删除某种关系,但与该关系相连的节点不受影响。
results = session.run("MATCH (a)-[r:ACTED_IN]->(b) RETURN a, r, b")
for record in results:
print(record.data())
print("***************************************************")
session.run("""MATCH (n:Person {name: 'Laurence Fishburne'})-[r:ACTED_IN]->() DELETE r""")
results = session.run("MATCH (a)-[r:ACTED_IN]->(b) RETURN a, r, b")
for record in results:
print(record.data())
{'a': {'name': 'Keanu Reever'}, 'r': ({'name': 'Keanu Reever'}, 'ACTED_IN', {'title': 'The Matrix'}), 'b': {'title': 'The Matrix'}} {'a': {'name': 'Laurence Fishburne'}, 'r': ({'name': 'Laurence Fishburne'}, 'ACTED_IN', {'title': 'The Matrix'}), 'b': {'title': 'The Matrix'}} {'a': {'name': 'Carrie-Anne Moss'}, 'r': ({'name': 'Carrie-Anne Moss'}, 'ACTED_IN', {'title': 'The Matrix'}), 'b': {'title': 'The Matrix'}} *************************************************** {'a': {'name': 'Keanu Reever'}, 'r': ({'name': 'Keanu Reever'}, 'ACTED_IN', {'title': 'The Matrix'}), 'b': {'title': 'The Matrix'}} {'a': {'name': 'Carrie-Anne Moss'}, 'r': ({'name': 'Carrie-Anne Moss'}, 'ACTED_IN', {'title': 'The Matrix'}), 'b': {'title': 'The Matrix'}}
In [25]:
session.run("""MATCH (n:Person {name: 'Carrie-Anne Moss'})
DELETE n""")
--------------------------------------------------------------------------- ConstraintError Traceback (most recent call last) Cell In[25], line 1 ----> 1 session.run("""MATCH (n:Person {name: 'Carrie-Anne Moss'}) 2 DELETE n""") File ~\.conda\envs\data_science\Lib\site-packages\neo4j\_sync\work\session.py:299, in Session.run(self, query, parameters, **kwargs) 295 raise ClientError("Explicit Transaction must be handled explicitly") 297 if self._auto_result: 298 # This will buffer upp all records for the previous auto-commit tx --> 299 self._auto_result._buffer_all() 301 if not self._connection: 302 self._connect(self._config.default_access_mode) File ~\.conda\envs\data_science\Lib\site-packages\neo4j\_sync\work\result.py:332, in Result._buffer_all(self) 328 def _buffer_all(self): 329 """Sets the Result object in an detached state by fetching all records 330 from the connection to the buffer. 331 """ --> 332 self._buffer() File ~\.conda\envs\data_science\Lib\site-packages\neo4j\_sync\work\result.py:318, in Result._buffer(self, n) 316 return 317 record_buffer = deque() --> 318 for record in self: 319 record_buffer.append(record) 320 if n is not None and len(record_buffer) >= n: File ~\.conda\envs\data_science\Lib\site-packages\neo4j\_sync\work\result.py:270, in Result.__iter__(self) 268 yield self._record_buffer.popleft() 269 elif self._streaming: --> 270 self._connection.fetch_message() 271 elif self._discarding: 272 self._discard() File ~\.conda\envs\data_science\Lib\site-packages\neo4j\_sync\io\_common.py:178, in ConnectionErrorHandler.__getattr__.<locals>.outer.<locals>.inner(*args, **kwargs) 176 def inner(*args, **kwargs): 177 try: --> 178 func(*args, **kwargs) 179 except (Neo4jError, ServiceUnavailable, SessionExpired) as exc: 180 assert not asyncio.iscoroutinefunction(self.__on_error) File ~\.conda\envs\data_science\Lib\site-packages\neo4j\_sync\io\_bolt.py:850, in Bolt.fetch_message(self) 846 # Receive exactly one message 847 tag, fields = self.inbox.pop( 848 hydration_hooks=self.responses[0].hydration_hooks 849 ) --> 850 res = self._process_message(tag, fields) 851 self.idle_since = monotonic() 852 return res File ~\.conda\envs\data_science\Lib\site-packages\neo4j\_sync\io\_bolt5.py:369, in Bolt5x0._process_message(self, tag, fields) 367 self._server_state_manager.state = self.bolt_states.FAILED 368 try: --> 369 response.on_failure(summary_metadata or {}) 370 except (ServiceUnavailable, DatabaseUnavailable): 371 if self.pool: File ~\.conda\envs\data_science\Lib\site-packages\neo4j\_sync\io\_common.py:245, in Response.on_failure(self, metadata) 243 handler = self.handlers.get("on_summary") 244 Util.callback(handler) --> 245 raise Neo4jError.hydrate(**metadata) ConstraintError: {code: Neo.ClientError.Schema.ConstraintValidationFailed} {message: Cannot delete node<9>, because it still has relationships. To delete this node, you must first delete its relationships.}
In [26]:
# 要删除节点及其连接的任何关系,请使用该DETACH DELETE子句
results = session.run("MATCH (a)-[r:ACTED_IN]->(b) RETURN a, r, b")
for record in results:
print(record.data())
print("*****************************************************")
session.run("""MATCH (n:Person {name: 'Carrie-Anne Moss'})
DETACH DELETE n""")
results = session.run("MATCH (a)-[r:ACTED_IN]->(b) RETURN a, r, b")
for record in results:
print(record.data())
{'a': {'name': 'Keanu Reever'}, 'r': ({'name': 'Keanu Reever'}, 'ACTED_IN', {'title': 'The Matrix'}), 'b': {'title': 'The Matrix'}} {'a': {'name': 'Carrie-Anne Moss'}, 'r': ({'name': 'Carrie-Anne Moss'}, 'ACTED_IN', {'title': 'The Matrix'}), 'b': {'title': 'The Matrix'}} ***************************************************** {'a': {'name': 'Keanu Reever'}, 'r': ({'name': 'Keanu Reever'}, 'ACTED_IN', {'title': 'The Matrix'}), 'b': {'title': 'The Matrix'}}
MERGE语句¶
- 该MERGE子句要么匹配图形中现有的节点模式并绑定它们,要么在不存在的情况下创建新数据并绑定它。这样,它就充当了和的组合MATCH,CREATE可以根据指定的数据是匹配还是创建来执行特定操作。
In [27]:
print_nodes()
session.run("""MERGE (robert:Critic {name: "Robert"})
RETURN robert""")
print("*********************************")
print_nodes()
{'n': {'title': 'The Matrix'}} {'n': {'name': 'Keanu Reever'}} {'n': {'name': 'Laurence Fishburne'}} ********************************* {'n': {'title': 'The Matrix'}} {'n': {'name': 'Robert'}} {'n': {'name': 'Keanu Reever'}} {'n': {'name': 'Laurence Fishburne'}}
In [28]:
print_nodes()
session.run("""MERGE (robert:Critic {name: "Robert"})
RETURN robert""")
print("*********************************")
print_nodes()
{'n': {'title': 'The Matrix'}} {'n': {'name': 'Robert'}} {'n': {'name': 'Keanu Reever'}} {'n': {'name': 'Laurence Fishburne'}} ********************************* {'n': {'title': 'The Matrix'}} {'n': {'name': 'Robert'}} {'n': {'name': 'Keanu Reever'}} {'n': {'name': 'Laurence Fishburne'}}
SET语句¶
- SET子句用于更新节点上的标签以及节点和关系上的属性
In [29]:
session.run("MATCH (n) DETACH DELETE n")
session.run("CREATE (n:Swedish {name: 'Andy'}), (m:German {name: 'Sara'})")
print_nodes()
{'n': {'name': 'Andy'}} {'n': {'name': 'Sara'}}
In [56]:
results = session.run("""MATCH (n {name: 'Andy'})
SET n.surname = 'Taylor'
RETURN n.name, n.surname""")
results.data()
Out[56]:
[{'n.name': 'Andy', 'n.surname': 'Taylor'}]
In [57]:
session.run("""MATCH(n {name: 'Andy'}), (m:German {name: 'Sara'})
CREATE (n)-[r:KNOWS]->(m)""")
Out[57]:
<neo4j._sync.work.result.Result at 0x21b50ebd9d0>
In [58]:
results = session.run("""MATCH (n:Swedish {name: 'Andy'})-[r:KNOWS]->(m:German {name: 'Sara'})
SET r.since = 1999
RETURN n, r, m, r.since""")
results.data()
Out[58]:
[{'n': {'surname': 'Taylor', 'name': 'Andy'}, 'r': ({'surname': 'Taylor', 'name': 'Andy'}, 'KNOWS', {'name': 'Sara'}), 'm': {'name': 'Sara'}, 'r.since': 1999}]
REMOVE语句¶
- 该REMOVE子句用于从节点和关系中删除属性,并从节点中删除标签。
In [66]:
session.run("MATCH (n) DETACH DELETE n")
session.run("""CREATE
(a:Swedish {name: 'Andy', age: 36, propTestValue1: 42}),
(t:Swedish {name: 'Timothy', age: 25, propTestValue2: 42}),
(p:German:Swedish {name: 'Peter', age: 34}),
(a)-[:KNOWS]->(t),
(a)-[:KNOWS]->(p)""")
results = session.run("MATCH (n) RETURN n, labels(n)")
results.data()
Out[66]:
[{'n': {'propTestValue1': 42, 'name': 'Andy', 'age': 36}, 'labels(n)': ['Swedish']}, {'n': {'propTestValue2': 42, 'name': 'Timothy', 'age': 25}, 'labels(n)': ['Swedish']}, {'n': {'name': 'Peter', 'age': 34}, 'labels(n)': ['Swedish', 'German']}]
In [67]:
results = session.run("""MATCH (a {name: 'Andy'})
REMOVE a.age
RETURN a.name, a.age""")
results.data()
Out[67]:
[{'a.name': 'Andy', 'a.age': None}]
ORDER BY语句¶
In [68]:
results = session.run("""MATCH (n)
RETURN n.name, n.age
ORDER BY n.name ASC""")
results.data()
Out[68]:
[{'n.name': 'Andy', 'n.age': None}, {'n.name': 'Peter', 'n.age': 34}, {'n.name': 'Timothy', 'n.age': 25}]
FOREACH函数¶
- FOREACH用于对集合中的每个元素执行某个操作。它非常适合在创建节点或关系时进行批量处理。
In [30]:
session.run("MATCH (n) DETACH DELETE n")
session.run("""WITH ["Alice", "Bob", "Charlie"] AS names
FOREACH (name IN names | CREATE (n:Person {name: name}))""")
print_nodes()
{'n': {'name': 'Bob'}} {'n': {'name': 'Charlie'}} {'n': {'name': 'Alice'}}
In [31]:
session.close()
driver.close()
neomodel¶
- Neomodel 是 Neo4j 的一个 Python ORM(对象关系映射)库,使得在 Python 中使用对象的方式操作图数据库变得更加简便。
- Neomodel 通过面向对象的结构封装了 Cypher 查询语句,简化了 Neo4j 数据库的使用,适合数据科学、机器学习项目中的图数据处理和知识图谱构建。
- https://neomodel.readthedocs.io/en/latest/getting_started.html
- 安装:
- pip install neomodel
In [32]:
from neomodel import (config, StructuredNode, StringProperty, IntegerProperty,
UniqueIdProperty, RelationshipTo)
config.DATABASE_URL = "bolt://neo4j:12345678@localhost:7687/neomodelexample"
In [33]:
class Country(StructuredNode):
code = StringProperty(unique_index=True, required=True)
class City(StructuredNode):
name = StringProperty(required=True)
country = RelationshipTo(Country, 'FROM_COUNTRY')
class Person(StructuredNode):
uid = UniqueIdProperty()
name = StringProperty(unique_index=True)
age = IntegerProperty(index=True, default=0)
# traverse outgoing IS_FROM relations, inflate to Country objects
country = RelationshipTo(Country, 'IS_FROM')
# traverse outgoing LIVES_IN relations, inflate to City objects
city = RelationshipTo(City, 'LIVES_IN')
In [34]:
from neomodel import db
db.cypher_query("MATCH (n) DETACH DELETE n")
# 创建节点
jim = Person(name='Jim', age=3).save() # Create
print(jim)
print(jim.age)
{'uid': '291b4d8e74e24ee9885137194964ff0a', 'name': 'Jim', 'age': 3, 'element_id_property': '4:44efcb72-4d1d-4a85-a35c-4a09712f17bd:1'} 3
In [35]:
# 更新节点
jim.age = 5
jim.save() # Update, (with validation)
jim.age
Out[35]:
5
In [36]:
# 查询所有节点
Person(name='Bob', age=5).save()
all_nodes = Person.nodes.all()
print(all_nodes)
[<Person: {'uid': '291b4d8e74e24ee9885137194964ff0a', 'name': 'Jim', 'age': 5, 'element_id_property': '4:44efcb72-4d1d-4a85-a35c-4a09712f17bd:1'}>, <Person: {'uid': 'caf2c5a9c90043dab63fdde91600cab3', 'name': 'Bob', 'age': 5, 'element_id_property': '4:44efcb72-4d1d-4a85-a35c-4a09712f17bd:2'}>]
In [37]:
# 通过特定属性查找节点
jim = Person.nodes.get(name='Jim')
jim
Out[37]:
<Person: {'uid': '291b4d8e74e24ee9885137194964ff0a', 'name': 'Jim', 'age': 5, 'element_id_property': '4:44efcb72-4d1d-4a85-a35c-4a09712f17bd:1'}>
In [38]:
# 创建关系
germany = Country(code='DE').save()
jim.country.connect(germany)
jim.country.all()
Out[38]:
[<Country: {'code': 'DE', 'element_id_property': '4:44efcb72-4d1d-4a85-a35c-4a09712f17bd:0'}>]
In [39]:
# 删除关系
jim.country.disconnect(germany)
jim.country.all()
Out[39]:
[]