📖 Neo4j基础
Neo4j是全球最流行的图数据库,采用属性图模型,使用Cypher查询语言。它原生存储图数据,提供了强大的图遍历和模式匹配能力。
🎯 Neo4j核心概念
- 节点(Node):实体,可以有多个标签(Label)和属性
- 关系(Relationship):有向边,必须有类型,可以有属性
- 属性(Property):键值对,附属于节点或关系
- 标签(Label):节点的分类标记,类似表名
- 路径(Path):节点和关系的交替序列
💻 Python实现:Neo4j风格的图数据库
class Neo4jStyleGraph:
"""Neo4j风格的属性图数据库(纯Python实现)"""
def __init__(self):
self.nodes = {}
self.rels = {}
self._nid = 0
self._rid = 0
self.label_index = defaultdict(set)
self.type_index = defaultdict(list)
def create_node(self, labels, properties=None):
"""CREATE (n:Label {props})"""
nid = self._nid; self._nid += 1
labels = set(labels) if isinstance(labels, (list, tuple)) else {labels}
self.nodes[nid] = {"labels": labels, "properties": properties or {}}
for label in labels:
self.label_index[label].add(nid)
return nid
def create_relationship(self, start, end, rel_type, properties=None):
"""CREATE (a)-[r:TYPE]->(b)"""
rid = self._rid; self._rid += 1
self.rels[rid] = {"type": rel_type, "start": start, "end": end, "properties": properties or {}}
self.type_index[rel_type].append(rid)
return rid
def match_nodes(self, label=None, where=None):
"""MATCH (n:Label) WHERE ... RETURN n"""
if label:
candidates = self.label_index.get(label, set())
else:
candidates = set(self.nodes.keys())
if where:
candidates = {n for n in candidates if where(self.nodes[n])}
return [(n, self.nodes[n]) for n in candidates]
def match_pattern(self, start_label, rel_type, end_label):
"""MATCH (a:Label1)-[r:TYPE]->(b:Label2)"""
results = []
for rid in self.type_index.get(rel_type, []):
rel = self.rels[rid]
start = self.nodes.get(rel["start"])
end = self.nodes.get(rel["end"])
if start and end:
start_match = not start_label or start_label in start["labels"]
end_match = not end_label or end_label in end["labels"]
if start_match and end_match:
results.append((rel["start"], rid, rel["end"]))
return results
def get_degree(self, node_id, direction="both"):
"""获取节点度数"""
degree = 0
for rel in self.rels.values():
if direction in ("out", "both") and rel["start"] == node_id:
degree += 1
if direction in ("in", "both") and rel["end"] == node_id:
degree += 1
return degree
def shortest_path(self, start_id, end_id):
"""BFS最短路径"""
from collections import deque
if start_id == end_id: return [start_id]
visited = {start_id}
queue = deque([(start_id, [start_id])])
while queue:
current, path = queue.popleft()
for rel in self.rels.values():
neighbor = None
if rel["start"] == current: neighbor = rel["end"]
elif rel["end"] == current: neighbor = rel["start"]
if neighbor is not None and neighbor not in visited:
new_path = path + [neighbor]
if neighbor == end_id: return new_path
visited.add(neighbor)
queue.append((neighbor, new_path))
return None
g = Neo4jStyleGraph()
g.create_node(["Person", "Director"], {"name": "郭帆", "born": 1980})
g.create_node(["Person", "Actor"], {"name": "吴京", "born": 1974})
g.create_node(["Person", "Actor"], {"name": "屈楚萧", "born": 1994})
g.create_node(["Movie"], {"title": "流浪地球", "year": 2019, "rating": 7.9})
g.create_node(["Movie"], {"title": "战狼2", "year": 2017, "rating": 7.1})
g.create_node(["Genre"], {"name": "科幻"})
g.create_node(["Genre"], {"name": "动作"})
g.create_relationship(0, 3, "DIRECTED")
g.create_relationship(1, 3, "ACTED_IN", {"role": "刘培强"})
g.create_relationship(2, 3, "ACTED_IN", {"role": "刘启"})
g.create_relationship(1, 4, "ACTED_IN", {"role": "冷锋"})
g.create_relationship(0, 4, "DIRECTED")
g.create_relationship(3, 5, "HAS_GENRE")
g.create_relationship(4, 6, "HAS_GENRE")
print("=== MATCH (n:Movie) ===")
for nid, node in g.match_nodes("Movie"):
print(f" {node['properties']}")
print("
=== MATCH (a:Actor)-[r:ACTED_IN]->(m:Movie) ===")
for start, rid, end in g.match_pattern("Actor", "ACTED_IN", "Movie"):
actor = g.nodes[start]["properties"]["name"]
movie = g.nodes[end]["properties"]["title"]
role = g.rels[rid]["properties"].get("role", "")
print(f" {actor} 饰演 {role} 在 《{movie}》")
print("
=== MATCH (n:Movie) WHERE n.rating > 7.5 ===")
for nid, node in g.match_nodes("Movie", where=lambda n: n["properties"].get("rating", 0) > 7.5):
print(f" {node['properties']['title']}: {node['properties']['rating']}")
print("
=== 度数统计 ===")
for nid in range(5):
name = g.nodes[nid]["properties"].get("name", g.nodes[nid]["properties"].get("title", nid))
deg = g.get_degree(nid)
print(f" {name}: 度={deg}")
=== MATCH (n:Movie) ===
{'title': '流浪地球', 'year': 2019, 'rating': 7.9}
{'title': '战狼2', 'year': 2017, 'rating': 7.1}
=== MATCH (a:Actor)-[r:ACTED_IN]->(m:Movie) ===
吴京 饰演 刘培强 在 《流浪地球》
屈楚萧 饰演 刘启 在 《流浪地球》
吴京 饰演 冷锋 在 《战狼2》
=== MATCH (n:Movie) WHERE n.rating > 7.5 ===
流浪地球: 7.9
=== 度数统计 ===
郭帆: 度=2
吴京: 度=2
屈楚萧: 度=1
流浪地球: 度=4
战狼2: 度=2
📝 实战练习
练习1:实现MERGE语义
实现MERGE操作:如果节点已存在则返回,否则创建。
练习2:聚合查询
实现类似Cypher的COUNT/DISTINCT聚合:统计每个演员参演的电影数。
练习3:最短路径
用shortest_path查找两个演员之间的合作路径(通过共同出演的电影)。
🔷
🏆 第12课成就解锁
Neo4j实践者
🔷 属性图
🔗 节点关系
🔍 MATCH查询
🛤️ 最短路径