2.3 关系穿越与变长路径 本节摘要:当"隔几层"不确定时,固定模式就不够用了。本节讲 Cypher 的三个纵深武器:变长路径 表达任意深度、OPTIONAL MATCH 实现"没找到也返回"的左连接语义、WITH 把一段漫游的中间结果传给下一段并就地聚合。三者合用,才能把开放式的图问题写成稳定可控的查询。 前两节的查询深度都是写死的。现实里最常见的图问题恰恰深度未知:社交链路隔几层、组织的汇报链有多长、资金穿了几道手。本节把漫游从"走一步、走两步"推进到"走到底、走成路径"。 变长路径: 的三种写法 在关系类型后加 ,即声明这一段可重复穿越: 变量把整条路径绑定下来, 取长度, 、 分别取节点序列与关系序列——排查"路径怎么走出来的"时这组函数最好用。
本节摘要:当"隔几层"不确定时,固定模式就不够用了。本节讲 Cypher 的三个纵深武器:变长路径
*1..n表达任意深度、OPTIONAL MATCH 实现"没找到也返回"的左连接语义、WITH 把一段漫游的中间结果传给下一段并就地聚合。三者合用,才能把开放式的图问题写成稳定可控的查询。
前两节的查询深度都是写死的。现实里最常见的图问题恰恰深度未知:社交链路隔几层、组织的汇报链有多长、资金穿了几道手。本节把漫游从"走一步、走两步"推进到"走到底、走成路径"。
* 的三种写法在关系类型后加 *,即声明这一段可重复穿越:
// 写法一:*n 恰好 n 层——恰好两层的推荐关系 MATCH (p:Person {name: 'Tom Hanks'})-[:ACTED_IN*2]->(m:Movie) RETURN p.name, m.title
p.name | m.title ------------|-------- "Tom Hanks" | "Cloud Atlas" -- 经由另一部影片中转的"二度关系",示例图数据不同结果各异
// 写法二:*1..3 一到三层;写法三:* 无上界(危险,稍后解释) MATCH path = (a:Person {name: 'Keanu Reeves'})-[:ACTED_IN*1..3]-(b:Person) WHERE a <> b RETURN length(path) AS 层数, count(*) AS 可达人数
层数 | 可达人数 -----|--------- 1 | 9 2 | 41 3 | 118
path 变量把整条路径绑定下来,length() 取长度,nodes(path)、relationships(path) 分别取节点序列与关系序列——排查"路径怎么走出来的"时这组函数最好用。

无上界的 * 是新手第一性能杀手。 稠密社交图上一个 * 可能展开百万级路径。纪律只有一条:永远给上界,配合 WHERE 在中途剪枝;确需"走到底"的场景,交给第 6 章的 GDS 图算法而不是裸 Cypher。
普通 MATCH 匹配失败会整行丢弃;OPTIONAL MATCH 则像 SQL 的左连接——主行保留,缺失部分填 null:
// 每部电影都返回;没导演的电影 director 为 null MATCH (m:Movie) OPTIONAL MATCH (d:Person)-[:DIRECTED]->(m) RETURN m.title, d.name AS director ORDER BY director IS NULL DESC LIMIT 4
m.title | director --------------------|--------- "Uncharted" | null "The Matrix" | "Lana Wachowski" "Forrest Gump" | "Robert Zemeckis"
典型用法是补全信息而非筛选主问题——先 MATCH 主干,再 OPTIONAL MATCH 挂装饰性的旁支,主结果不会被旁支缺席绑架。
WITH 是查询内部的管道口:它像 RETURN 一样聚合、排序、筛选中间结果,再把结果作为下一程 MATCH 的输入。一个两段式问题:"先找出参演人数最多的电影,再找这些电影里 1970 年后出生的演员":
MATCH (:Person)-[:ACTED_IN]->(m:Movie) WITH m, count(*) AS castSize WHERE castSize >= 3 MATCH (a:Person)-[:ACTED_IN]->(m) WHERE a.born >= 1970 RETURN m.title, collect(a.name) AS 年轻演员
m.title | 年轻演员 -----------------|------------------------ "The Matrix" | ["Keanu Reeves", "Carrie-Anne Moss"]
第一段算出每部电影的人数,WHERE castSize >= 3 就地过滤;第二段在缩小后的电影集合上继续漫游,最后 collect 收演员名单。没有 WITH,这两件事只能拆成两条查询在应用层拼接。
WITH 后接 DISTINCT 或聚合,是消除多路 MATCH 笛卡尔积的标准解法:先在 WITH 处收拢成"每部一行",再展开下一段。
把三件武器合起来,重写 1.1 里那道产品题,并输出推荐理由:
// 两段式:先收拢二度人脉及其喜欢的电影,再剔除已看过 MATCH (me:Person {name: 'Alice'})-[:FRIENDS_WITH*1..2]-(friend:Person) WHERE friend <> me WITH DISTINCT me, friend MATCH (friend)-[:LIKES]->(m:Movie) WHERE NOT (me)-[:WATCHED]->(m) RETURN m.title, count(friend) AS 支持人数 ORDER BY 支持人数 DESC, m.title
m.title | 支持人数 ------------------|--------- "The Matrix" | 3 "Inception" | 2
变式:想知道推荐理由来自哪一层的连接,把 *1..2 改写成两段独立 MATCH 分别计数即可;想知道路径本身,给第一程绑定 path 变量并输出 nodes(path)。