概述
OPTIONAL MATCH的作用与MATCH类似,都用于实现图模式匹配,它们的不同之处在于:
MATCH:如果一个图模式没有匹配结果,不返回任何记录。OPTIONAL MATCH:如果一个图模式没有匹配结果,返回一个null值。
<optional match statement> ::=
"OPTIONAL" <match statement>
| "OPTIONAL" "{" <match statement>... "}"
| "OPTIONAL" "(" <match statement>... ")"
如果OPTIONAL MATCH中的图模式使用了之前语句中声明过的变量,那么它会隐式地进行子查询。此时,OPTIONAL MATCH对该变量的记录逐条进行图模式匹配,返回匹配结果或null。
注意:使用OPTIONAL MATCH时,WHERE子句是在图模式匹配阶段就被评估的,也就是说,它在OPTIONAL逻辑生效之前就已经起作用了。
示例图

CREATE GRAPH myGraph {
NODE User ({name string}),
NODE Club ({since uint32}),
EDGE Follows ()-[{createdOn date}]->(),
EDGE Joins ()-[{memberNo uint32}]->()
} PARTITION BY HASH(Crc32) SHARDS [1]
INSERT (rowlock:User {_id: 'U01', name: 'rowlock'}),
(brainy:User {_id: 'U02', name: 'Brainy'}),
(purplechalk:User {_id: 'U03', name: 'purplechalk'}),
(mochaeach:User {_id: 'U04', name: 'mochaeach'}),
(lionbower:User {_id: 'U05', name: 'lionbower'}),
(c01:Club {_id: 'C01', since: 2005}),
(c02:Club {_id: 'C02', since: 2005}),
(rowlock)-[:Follows {createdOn: '2024-01-05'}]->(brainy),
(mochaeach)-[:Follows {createdOn: '2024-02-10'}]->(brainy),
(brainy)-[:Follows {createdOn: '2024-02-01'}]->(purplechalk),
(purplechalk)-[:Follows {createdOn: '2024-05-03'}]->(lionbower),
(brainy)-[:Joins {memberNo: 1}]->(c01),
(lionbower)-[:Joins {memberNo: 2}]->(c01),
(mochaeach)-[:Joins {memberNo: 9}]->(c02)
检查存在性
用户rowlock没有加入俱乐部C01,因此这个查询不返回任何结果:
MATCH (:User {name: "rowlock"})->(c:Club {_id: "C01"})
RETURN c
结果:无数据返回
然而,使用OPTIONAL MATCH时,查询返回一行null记录,代表没有匹配结果:
OPTIONAL MATCH (:User {name: "rowlock"})->(c:Club {_id: "C01"})
RETURN c
结果:
| c |
|---|
null |
保留所有流入查询的记录
此查询中,两个MATCH语句基于共同变量u进行等值连接(Equi-join):
MATCH (u:User)
MATCH (u)-[:Joins]->(c:Club)
RETURN u.name, c._id
结果:
| u.name | c._id |
|---|---|
| mochaeach | C02 |
| Brainy | C01 |
| lionbower | C01 |
为保留所有用户,无论他们是否加入了某个俱乐部,你可以将第二个MATCH替换成OPTIONAL MATCH。第一个MATCH获取的每个用户点依次流入OPTIONAL MATCH,即使没有匹配结果,用户点也不会被舍弃:
MATCH (u:User)
OPTIONAL MATCH (u)-[:Joins]->(c:Club)
RETURN u.name, c._id
结果:
| u.name | c._id |
|---|---|
| mochaeach | C02 |
| Brainy | C01 |
| rowlock | null |
| lionbower | C01 |
| purplechalk | null |
保持查询运行
当某条语句产生空结果时,查询会在此处停止执行,因为后续语句将没有数据可供操作。
例如,以下查询将不会返回任何结果,因为MATCH找不到匹配的点,导致RETURN语句根本不会被执行:
MATCH (u:User) WHERE u.name = "Masterpiece1989"
RETURN CASE WHEN u IS NULL THEN "User not found" ELSE u END
结果:无数据返回
为了确保即使在未找到匹配项时查询仍能继续执行,可以使用OPTIONAL MATCH:
OPTIONAL MATCH (u:User) WHERE u.name = "Masterpiece1989"
RETURN CASE WHEN u IS NULL THEN "User not found" ELSE u END
结果:User not found
WHERE子句的评估
本查询返回没有关注者的用户:
MATCH (n:User)
OPTIONAL MATCH p = (n)<-[:Follows]-()
FILTER p IS NULL
RETURN COLLECT_LIST(n.name) AS Names
结果:
| Names |
|---|
| ["mochaeach","rowlock"] |
如果将FILTER替换为WHERE,将无法获得预期结果,因为WHERE子句是在OPTIONAL生效之前就被评估的:
MATCH (n:User)
OPTIONAL MATCH p = (n)<-[:Follows]-()
WHERE p IS NULL
RETURN COLLECT_LIST(n.name) AS Names
结果:
| Names |
|---|
| ["mochaeach","Brainy","rowlock","lionbower","purplechalk"] |
使用MATCH语句块
你可以使用花括号{}或圆括号()将多条MATCH语句包裹起来,并将OPTIONAL应用于整个MATCH语句块。这样,整个语句块会被视为一个整体:如果其中任何一部分匹配失败,查询不会中断,而是为该块中引入的所有变量返回null。
FOR name IN ["rowlock", "Masterpiece1989", "Brainy"]
OPTIONAL {
MATCH (u:User) WHERE u.name = name
MATCH (u)->(c:Club)
}
RETURN table(name, u.name, c._id)
结果:
| name | u.name | c._id |
|---|---|---|
| rowlock | null |
null |
| Masterpiece1989 | null |
null |
| Brainy | Brainy | C01 |