对多个节点执行模式否定
某些用例需要匹配那些未与另一组节点中的任意节点相连的节点。我们将讨论此类查询的错误做法和正确做法。
在我们的示例中,我们将使用一个食谱图,其主要结构如下
(:Recipe)-[:INCLUDES]->(:Ingredient)
使用场景是,提供一个要排除的配料名称列表,匹配不包含这些配料的食谱。
错误做法:在排除检测时让节点各自占一行
一种常见的错误做法是不使用集合,而错误地假设被否定的模式会把变量值当作集合处理。
MATCH (excluded:Ingredient)
WHERE excluded.name in $excludedIngredients
MATCH (r:Recipe)
WHERE NOT (r)-[:INCLUDES]->(excluded)
RETURN r
对于单个要排除的配料,上述查询可以正常工作。
但是,当 $excludedIngredients 包含多个条目时,该查询可能无法得到正确的结果。
原因在于每个要排除的配料都占据单独的记录/行,并会被逐一测试,这会导致某些只包含部分要排除配料的食谱没有被正确过滤。
例如,假设参数如下: {excludedIngredients:['eggs', 'walnuts']}
假设正在评估的 Chocolate Cake :Recipe 包含鸡蛋,但不包含核桃。
因为每个要排除的配料各自占一条记录,如果在评估时检查构建的记录,可能会呈现如下:
| excluded.name | r.name |
|---|---|
|
|
|
|
这些构建的记录是针对每个单独配料以及包含该配料的每个食谱生成的。它们不会自动组织成集合(若需集合需使用 collect())。
每条记录都会被独立评估:WHERE NOT (r)-[:INCLUDES]→(excluded)
第一条记录会被评估,由于 Chocolate Cake 包含鸡蛋,该记录会被剔除。
第二条记录会被评估,由于 Chocolate Cake 不包含核桃,该记录会被保留。
于是 Chocolate Cake :Recipe 会作为结果返回,这显然是错误的。
正确做法:收集要排除的节点,并在集合上使用 WHERE NONE() 进行排除
正确的做法是利用集合成员关系来实现排除。实际上,有许多基于此思路的正确查询,它们的效率可能因具体图结构而异。
MATCH (excluded:Ingredient)
WHERE excluded.name in $excludedIngredients
WITH collect(excluded) as excluded
MATCH (r:Recipe)-[:INCLUDES]->(i)
WITH excluded, r, collect(i) as ingredients
WHERE NONE (i in ingredients where i in excluded)
RETURN r
我们可以使用模式推导一次性展开并收集所有节点。
MATCH (excluded:Ingredient)
WHERE excluded.name in $excludedIngredients
WITH collect(excluded) as excluded
MATCH (r:Recipe)
WITH excluded, r, [(r)-[:INCLUDES]->(i) | i] as ingredients
WHERE NONE (i in ingredients where i in excluded)
RETURN r
或者,我们也可以避免为每个食谱收集配料,而是直接使用 NONE() 来排除任何包含在要排除配料集合中的关系模式。
MATCH (excluded:Ingredient)
WHERE excluded.name in $excludedIngredients
WITH collect(excluded) as excluded
MATCH (r:Recipe)
WHERE NONE(i in excluded WHERE (r)-[:INCLUDES]->(i))
RETURN r
另一种正确做法:使用 OPTIONAL MATCH 匹配排除集合中的成员,并排除非空值
另一种做法不使用 WHERE NONE(),而是从 :Recipe 出发进行 OPTIONAL MATCH,但仅匹配我们想要排除的节点。
如果没有匹配到任何要排除的节点,新引入的变量 i 将为 null,我们可以通过另一条 WHERE 子句过滤,只保留这些行。
MATCH (excluded:Ingredient)
WHERE excluded.name in $excludedIngredients
WITH collect(excluded) as excluded
MATCH (r:Recipe)
OPTIONAL MATCH (r)-[:INCLUDES]->(i)
WHERE i in excluded
WITH r
WHERE i IS NULL
RETURN r
此页面有帮助吗?