一、高级 Cypher 模式匹配(Pattern Matching)
1.1 基础回顾
Cypher 的核心在于模式匹配,通过声明式语法描述图结构。例如:
MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) RETURN p.name,r.date, m.title
Person 节点 p 别名
Movie 节点 m 别名
ACTED_IN 自定义关系 r 别名
p.name p节点的name属性, r.date r关系的date属性, m.title m节点的title属性
该语句查找所有“人”节点通过 ACTED_IN 关系连接到“电影”节点的路径。
1.2 可变长度路径(Variable-Length Paths)
语法:
[:REL_TYPE*min..max]
*1..3:表示路径长度为 1 到 3 跳(即经过 1 至 3 条关系)*2:等价于*2..2*..5:从 0 到 5 跳(注意:0 跳表示起点=终点)*:无限制长度(慎用,可能性能爆炸)
示例:
查找某演员通过共同参演电影最多 3 层间接关联的所有其他演员:
MATCH (a:Person {name: "Tom Hanks"})-[:ACTED_IN*1..3]-(coActor:Person)
RETURN DISTINCT coActor.name
⚠️ 注意:可变长度路径默认不重复访问同一节点(避免环路),但若需允许环路,可使用 APOC 的
apoc.path.expandConfig。
⚠️说明:可变路径只对存在关联的节点生效,不存在关联的节点哪怕某些属性一致也不生效,比如
CREATE (u1:user{id:1,name:'张三'})
CREATE (u2:user{id:2,name:'李四'})
CREATE (u3:user{id:3,name:'王五'})
CREATE (l1:loan{id:1,amount:1000})
CREATE (u1)-[:HAS_LOAN]->(l1)
CREATE (l1)-[:HAS_CONTACT]->(u2)
CREATE (l1)-[:HAS_CONTACT]->(u3)
CREATE (l2:loan{id:2,amount:1000})
CREATE (u4:user{id:4,name:'赵六'})
CREATE (u2)-[:HAS_LOAN]->(l2)
CREATE (l2)-[:HAS_CONTACT]->(u4)
CREATE (l3:loan{id:3,amount:1000})
CREATE (u4)-[:HAS_LOAN]->(l3)
因为涉及 (user)-[:HAS_LOAN]->(loan)-[:HAS_CONTACT]->(user)-[:HAS_LOAN]->(loan)-[:HAS_CONTACT]->(user)... 所以要可变路径+模式匹配
Cypher 本身不支持“交替关系类型”的可变长度路径,所以需要用APOC 库,
MATCH (start:user {name: '张三'})
CALL apoc.path.expandConfig(start, {
relationshipFilter: 'HAS_LOAN>|HAS_CONTACT>',
labelFilter: '+user|+loan',
minLevel: 1,
maxLevel: 10,
uniqueness: 'NODE_GLOBAL'
})
YIELD path
WITH path, nodes(path) AS ns
// 遍历路径中的每个节点,找出 loan 节点,并计算它属于第几层(从张三开始算第0层用户)
UNWIND range(1, size(ns) - 1) AS idx
WITH ns[idx] AS node, idx
WHERE node:loan
// 层级定义:张三为第0层用户,他的贷款是第1跳,联系人是第2跳,联系人的贷款是第3跳 → 所以 loan 出现在奇数跳
// 第 n 层贷款(n=1,2,3...)对应 idx = 2*n - 1
WITH node AS loanNode,
toInteger((idx + 1) / 2) AS loanLevel // 第1层贷款(张三自己的)、第2层(联系人的)、第3层(联系人的联系人的)...
MATCH (owner:user)-[:HAS_LOAN]->(loanNode)
RETURN
loanLevel,
owner.name AS 贷款人,
loanNode.id AS 贷款ID,
loanNode.amount AS 金额
ORDER BY loanLevel, 贷款人
MATCH (u0:user {name:'张三'})-[:HAS_LOAN]->(l1:loan)
MATCH (l1)-[:HAS_CONTACT]->(u1:user)
OPTIONAL MATCH (u1)-[:HAS_LOAN]->(l2:loan)
OPTIONAL MATCH (l2)-[:HAS_CONTACT]->(u2:user)
OPTIONAL MATCH (u2)-[:HAS_LOAN]->(l3:loan)
OPTIONAL MATCH (l3)-[:HAS_CONTACT]->(u3:user)
OPTIONAL MATCH (u3)-[:HAS_LOAN]->(l4:loan)
...
二、聚合函数(Aggregation Functions)
Cypher 支持 SQL 风格的聚合操作,自动按非聚合字段分组。
2.1 常用聚合函数
| 函数 | 说明 |
|---|---|
COUNT(*) / COUNT(expr) |
计数(忽略 null) |
COLLECT(expr) |
将值收集为列表 |
DISTINCT expr |
去重(常用于 RETURN 或 WITH) |
2.2 示例
统计每位演员参演电影数量:
MATCH (p:Person)-[:ACTED_IN]->(m:Movie) RETURN p.name, COUNT(m) AS movieCount ORDER BY movieCount DESC
收集所有合作演员(去重):
MATCH (p:Person {name: "Keanu Reeves"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(co:Person)
RETURN p.name, COLLECT(DISTINCT co.name) AS coActors
💡 提示:
DISTINCT可用于COLLECT(DISTINCT x)避免重复元素。
三、WITH 子句:中间结果处理
WITH 允许将查询分阶段处理,传递中间结果到下一阶段。
3.1 基本用法
MATCH (p:Person)
WITH p WHERE p.born < 1970
MATCH (p)-[:ACTED_IN]->(m)
RETURN p.name, m.title
3.2 高级场景:限制后继续匹配
找出参演电影最多的前 3 位演员,并列出他们的电影:
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WITH p, COUNT(m) AS cnt
ORDER BY cnt DESC
LIMIT 3
MATCH (p)-[:ACTED_IN]->(m)
RETURN p.name, COLLECT(m.title) AS movies
✅
WITH是实现复杂逻辑流水线的关键。
四、UNWIND 与 FOREACH
4.1 UNWIND:展开列表
将列表“打平”为多行,便于后续处理。
UNWIND [1, 2, 3] AS num
RETURN num * 2
// 结果:2, 4, 6
实战:批量创建节点
UNWIND ["Alice", "Bob", "Charlie"] AS name
CREATE (:Person {name: name})
4.2 FOREACH:对列表执行副作用操作
仅用于写操作(如 CREATE、SET、DELETE),不能返回数据。
MATCH (p:Person {name: "Alice"})
FOREACH (x IN ["Movie1", "Movie2"] |
CREATE (p)-[:ACTED_IN]->(:Movie {title: x})
)
⚠️
FOREACH不支持MATCH或RETURN,仅用于副作用。
五、索引与约束(Performance & Data Integrity)
5.1 索引(Index)
加速属性查找:
// 创建 B-tree 索引(默认)
CREATE INDEX person_name FOR (p:Person) ON (p.name)
// 全文索引(支持模糊搜索)
CREATE FULLTEXT INDEX movie_title FOR (m:Movie) ON EACH [m.title]
查询时自动使用索引(如
WHERE p.name = 'Tom')。
5.2 约束(Constraint)
确保数据完整性:
// 唯一性约束
CREATE CONSTRAINT person_email_unique FOR (p:Person) REQUIRE p.email IS UNIQUE
// 节点属性存在性约束(企业版)
CREATE CONSTRAINT person_must_have_name FOR (p:Person) REQUIRE p.name IS NOT NULL
唯一性约束自动创建索引,无需单独建索引。
六、APOC 库简介(Awesome Procedures on Cypher)
APOC 是 Neo4j 的官方扩展库,提供数百个实用过程和函数。
6.1 安装(Neo4j Desktop 或 conf/neo4j.conf 启用)
dbms.security.procedures.unrestricted=apoc.*
6.2 常用功能示例
加载 JSON 数据:
CALL apoc.load.json("https://api.example.com/movies")
YIELD value
CREATE (:Movie {
title: value.title,
year: value.year
})
路径扩展(带配置):
MATCH (start:Person {name: "Tom Hanks"})
CALL apoc.path.expandConfig(start, {
relationshipFilter: "ACTED_IN>",
minLevel: 1,
maxLevel: 4,
uniqueness: "NODE_GLOBAL"
})
YIELD path
RETURN path
虚拟节点/关系(用于可视化中间结果):
CALL apoc.create.vNode(['Summary'], {count: 42}) YIELD node
RETURN node
📘 官方文档:https://neo4j.com/labs/apoc/
七、图建模设计原则
7.1 如何设计节点/关系结构?
黄金法则:
“如果某事物会拥有属性、会被查询、或会参与多个关系 → 应建为节点。”
示例对比:
✅ 好设计:
(:Person)-[:DIRECTED]->(:Movie)
(:Person)-[:ACTED_IN]->(:Movie)
(:Movie)-[:IN_GENRE]->(:Genre)
❌ 坏设计(过度属性化):
(:Person {directedMovies: ["Matrix", "John Wick"]})
→ 无法高效查询“谁导演了 Matrix?”或建立类型关系。
7.2 避免反模式(Anti-Patterns)
| 反模式 | 问题 | 正确做法 |
|---|---|---|
| 用属性代替关系 | 丧失图遍历能力 | 将交互建模为关系 |
| 过度泛化节点标签(如全用 :Entity) | 失去语义,索引失效 | 使用具体标签如 :User, :Product |
| 在关系上存储大量属性 | 关系不可索引 | 若需查询,考虑转为节点(关系实体化) |
7.3 实体 vs. 关系 vs. 属性的权衡
| 场景 | 建模建议 |
|---|---|
| “用户购买商品” | (User)-[:PURCHASED {date: ..., qty: 2}]->(Product) ✅(关系带属性) |
| “订单”有独立生命周期 | (User)-[:PLACED]->(Order)-[:CONTAINS]->(Product) ✅(订单作为节点) |
| “国家属于大洲” | (Country)-[:PART_OF]->(Continent) ✅(关系)而非 Country.continent = "Asia" ❌ |
| 枚举值(如状态) | 用属性:Order.status = "shipped" ✅ |
| 多对多分类(如标签) | (Item)-[:HAS_TAG]->(:Tag {name: "urgent"}) ✅ |
🔑 核心思想:让频繁查询的路径成为自然的图遍历路径。
八、实战练习建议
- 建模练习:将电商系统(用户、订单、商品、评论)建模为图。
- Cypher 挑战:
- 找出两个用户之间的最短合作路径(演员网络)
- 使用
WITH + COLLECT实现“每个导演的代表作(评分最高)”
- 性能优化:
- 为常用查询字段添加索引
- 使用
EXPLAIN分析执行计划
📌 总结:高级 Cypher 的核心是组合模式匹配、聚合、流程控制(WITH)与扩展库(APOC),而优秀图建模的关键在于忠实反映领域语义,避免关系数据库思维惯性。
浙公网安备 33010602011771号