知识库

对多个节点执行模式否定

某些用例需要匹配未连接到其他任何节点集的节点。我们将讨论此类查询的错误和正确方法。

在我们的示例中,我们将使用食谱图,其主要结构如下

(: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

eggs

Chocolate Cake

walnuts

Chocolate Cake

构建的记录是每个单一配料与包含它的每个食谱的组合。它们不会自动排列成集合(这需要使用 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
© . All rights reserved.