WITH

WITH 子句在 Cypher® 中有多种用途

自 Cypher 25 起,在写操作和读操作子句之间不再强制要求使用 WITH 作为分隔符。

示例图

以下示例使用了具有以下模式的图

要重建该图,请对空的 Neo4j 数据库运行以下查询。

CREATE (techCorp:Supplier {name: 'TechCorp', email: 'contact@techcorp.com'}),
       (foodies:Supplier {name: 'Foodies Inc.', email: 'info@foodies.com'}),

       (laptop:Product {name: 'Laptop', price: 1000}),
       (phone:Product {name: 'Phone', price: 500}),
       (headphones:Product {name: 'Headphones', price: 250}),
       (chocolate:Product {name: 'Chocolate', price: 5}),
       (coffee:Product {name: 'Coffee', price: 10}),

       (amir:Customer {firstName: 'Amir', lastName: 'Rahman', email: 'amir.rahman@example.com', discount: 0.1}),
       (keisha:Customer {firstName: 'Keisha', lastName: 'Nguyen', email: 'keisha.nguyen@example.com', discount: 0.2}),
       (mateo:Customer {firstName: 'Mateo', lastName: 'Ortega', email: 'mateo.ortega@example.com', discount: 0.05}),
       (hannah:Customer {firstName: 'Hannah', lastName: 'Connor', email: 'hannah.connor@example.com', discount: 0.15}),
       (leila:Customer {firstName: 'Leila', lastName: 'Haddad', email: 'leila.haddad@example.com', discount: 0.1}),
       (niko:Customer {firstName: 'Niko', lastName: 'Petrov', email: 'niko.petrov@example.com', discount: 0.25}),
       (yusuf:Customer {firstName: 'Yusuf', lastName: 'Abdi', email: 'yusuf.abdi@example.com', discount: 0.1}),

       (amir)-[:BUYS {date: date('2024-10-09')}]->(laptop),
       (amir)-[:BUYS {date: date('2025-01-10')}]->(chocolate),
       (keisha)-[:BUYS {date: date('2023-07-09')}]->(headphones),
       (mateo)-[:BUYS {date: date('2025-03-05')}]->(chocolate),
       (mateo)-[:BUYS {date: date('2025-03-05')}]->(coffee),
       (mateo)-[:BUYS {date: date('2024-04-11')}]->(laptop),
       (hannah)-[:BUYS {date: date('2023-12-11')}]->(coffee),
       (hannah)-[:BUYS {date: date('2024-06-02')}]->(headphones),
       (leila)-[:BUYS {date: date('2023-05-17')}]->(laptop),
       (niko)-[:BUYS {date: date('2025-02-27')}]->(phone),
       (niko)-[:BUYS {date: date('2024-08-23')}]->(headphones),
       (niko)-[:BUYS {date: date('2024-12-24')}]->(coffee),
       (yusuf)-[:BUYS {date: date('2024-12-24')}]->(chocolate),
       (yusuf)-[:BUYS {date: date('2025-01-02')}]->(laptop),

       (techCorp)-[:SUPPLIES]->(laptop),
       (techCorp)-[:SUPPLIES]->(phone),
       (techCorp)-[:SUPPLIES]->(headphones),
       (foodies)-[:SUPPLIES]->(chocolate),
       (foodies)-[:SUPPLIES]->(coffee)

创建新变量

WITH 可以与 AS 关键字结合使用,以绑定可传递给后续子句的新变量。

创建新变量
WITH [1, 2, 3] AS list
RETURN list
结果
list

[1, 2, 3]

行:1

在下面的示例中,WITH 子句将所有匹配到的 Customer 节点绑定到一个新变量 customers。绑定的节点是 MAP 值,之后可以通过该新变量进行引用。

创建绑定到匹配节点的新变量
MATCH (c:Customer)-[:BUYS]->(:Product {name: 'Chocolate'})
WITH c AS customers
RETURN customers.firstName AS chocolateCustomers
结果
chocolateCustomers

"Amir"

"Mateo"

"Yusuf"

行:3

控制作用域中的变量

WITH 可用于控制哪些变量保留在查询的作用域内。任何被 WITH 子句引用的变量都会保留在查询作用域内,并可供后续子句使用。如果变量在 WITH 子句中被重命名,则后续子句只能通过新名称引用它。如果变量未在 WITH 子句中显式引用,它将从查询作用域中移除,且后续子句无法再引用它。若要保留作用域内的所有变量,请使用 WITH *

在下面的查询中,WITH 子句移除了 p 变量的作用域。因此,后续的 RETURN 子句将无法使用它。同样,c 变量也无法使用——由于之前的 WITH 子句,仅有 chocolateCustomers 可用。

移除变量的作用域
MATCH (c:Customer)-[:BUYS]->(p:Product {name: 'Chocolate'})
WITH c.name AS chocolateCustomers
RETURN chocolateCustomers,
       p.price AS chocolatePrice
GQLSTATUS 错误链

42N62: 错误: 语法错误或访问规则冲突 - 变量未定义。变量 p 未定义。

42001:错误:语法错误或访问规则冲突 - 无效语法

使用 WITH * 保留所有变量
MATCH (supplier:Supplier)-[r]->(product:Product)
WITH *
RETURN supplier.name AS company,
       type(r) AS relType,
       product.name AS product
结果
company relType product

"TechCorp"

"SUPPLIES"

"Laptop"

"TechCorp"

"SUPPLIES"

"Phone"

"TechCorp"

"SUPPLIES"

"Headphones"

"Foodies Inc."

"SUPPLIES"

"Chocolate"

"Foodies Inc."

"SUPPLIES"

"Coffee"

行:5

WITH 不能移除导入到 CALL 子查询 中的变量的作用域,因为导入到子查询中的变量在其内部作用域中被视为全局变量。更具体地说,导入 CALL 子查询的变量即使在前面的 WITH 子句中未被引用,也可供后续子句使用。

在下面的示例中,x 变量被导入到 CALL 子查询的内部作用域中,即使前面的 WITH 子句忽略了它,RETURN 子句依然可以成功引用它。

子查询内部作用域中的变量无法被移除
WITH 11 AS x
CALL (x) {
  UNWIND [2, 3] AS y
  WITH y
  RETURN x*y AS a
}
RETURN x, a
结果
x a

11

22

11

33

行:2

更多信息,请参阅 CALL 子查询 → 导入变量

将值绑定到变量

WITH 可用于将表达式的值赋给变量。在下面的查询中,STRING 连接表达式的值被绑定到新变量 customerFullName,表达式 chocolate.price * (1 - customer.discount) 的值被绑定到 chocolateNetPrice,这两个变量随后都可在 RETURN 子句中使用。

将值绑定到变量
MATCH (customer:Customer)-[:BUYS]->(chocolate:Product {name: 'Chocolate'})
WITH customer.firstName || ' ' || customer.lastName AS customerFullName,
     chocolate.price * (1 - customer.discount) AS chocolateNetPrice
RETURN customerFullName,
       chocolateNetPrice
结果
customerFullName chocolateNetPrice

"Amir Rahman"

4.5

"Mateo Ortega"

4.75

"Yusuf Abdi"

4.5

行:3

由于 WITH 可用于将变量赋值给表达式的值,因此它可用于链式表达式。

使用 WITH 进行链式表达式
MATCH (p:Product)
WITH p, p.price >= 500 AS isExpensive
WITH p, isExpensive, NOT isExpensive AS isAffordable
WITH p, isExpensive, isAffordable,
     CASE
         WHEN isExpensive THEN 'High-end'
         ELSE 'Budget'
     END AS discountCategory
RETURN p.name AS product,
       p.price AS price,
       isAffordable,
       discountCategory
ORDER BY price
结果
product 价格 isAffordable discountCategory

"Chocolate"

5

true

'Budget'

"Coffee"

10

true

'Budget'

"Headphones"

250

true

'Budget'

"Phone"

500

false

'High-end'

"Laptop"

1000

false

'High-end'

行:5

LET 子句也可用于给变量赋值和链式表达式,且比 WITH 更清晰简洁。更多信息,请参阅 LET → 链式表达式

聚合

WITH 子句可以执行聚合并将结果绑定到新变量。在此示例中,使用 sum() 函数计算每个客户的总消费额,并将每个结果绑定到新变量 totalSpent。使用 collect() 函数将每个产品收集到绑定到 productsBought 变量的 LIST 值中。

WITH 执行聚合
MATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c.firstName AS customer,
     sum(p.price) AS totalSpent,
     collect(p.name) AS productsBought
RETURN customer,
       totalSpent,
       productsBought
ORDER BY totalSpent DESC
结果
customer totalSpent productsBought

"Mateo"

1015

["Laptop", "Chocolate", "Coffee"]

"Amir"

1005

["Laptop", "Chocolate"]

"Yusuf"

1005

["Laptop", "Chocolate"]

"Leila"

1000

["Laptop"]

"Niko"

760

["Phone", "Headphones", "Coffee"]

"Hannah"

260

["Headphones", "Coffee"]

"Keisha"

250

["Headphones"]

行:7

移除重复值

如果使用 DISTINCT 修饰符,WITH 可用于从结果集中移除重复值。

在下面的查询中,WITH DISTINCT 用于从 Customer 节点中移除重复的 discount 属性值。

使用 WITH DISTINCT 移除重复值
MATCH (c:Customer)
WITH DISTINCT c.discount AS discountRates
RETURN discountRates
ORDER BY discountRates
结果
discountRates

0.05

0.1

0.15

0.2

0.25

行:5

显式投影值

WITH ALL 可用于显式投影绑定到变量的所有值。其功能与使用普通的 WITH 相同。

使用 WITH ALL 进行显式结果投影
MATCH (c:Customer)
WITH ALL c.discount AS discountRates
RETURN discountRates
ORDER BY discountRates
结果
discountRates

0.05

0.1

0.1

0.1

0.15

0.2

0.25

行:7

排序与分页

如果与 ORDER BYLIMITSKIP 子句配合使用,WITH 可以对结果进行排序和分页。在这种情况下,这些子句应理解为 WITH 执行的结果操作的一部分——而不是独立的子句——在将结果传递给后续子句之前进行处理。

在下面的查询中,结果先使用 ORDER BYCustomer 的总消费额降序排列,然后再传递给最后的 RETURN 子句。

使用 ORDER BY 对结果进行排序
MATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c,
     sum(p.price) AS totalSpent
  ORDER BY totalSpent DESC
RETURN c.firstName AS customer, totalSpent
结果
customer totalSpent

"Mateo"

1015

"Amir"

1005

"Yusuf"

1005

"Leila"

1000

"Niko"

760

"Hannah"

260

"Keisha"

250

行:7

在下一个示例中,LIMIT 用于在排序后仅保留 totalSpent 值最高的 3 位客户。然后,SET 将一个新属性 (topSpender = true) 分配给消费最高的那些客户。

使用 LIMIT 限制结果
MATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c,
     sum(p.price) AS totalSpent
  ORDER BY totalSpent DESC
  LIMIT 3
SET c.topSpender = true
RETURN c.firstName AS customer,
       totalSpent,
       c.topSpender AS topSpender
customer totalSpent topSpender

"Mateo"

1015

true

"Amir"

1005

true

"Yusuf"

1005

true

行:3

SKIP 可在 WITH 子句之后使用,以从结果集中丢弃行。下面,SKIP 排除了已排序结果集中的前 3 行(即 totalSpent 值最高的 3 个 Customer 节点),并将 false 值分配给剩余 Customer 节点的新 topSpender 属性。

使用 SKIP 排除结果
MATCH (c:Customer)-[:BUYS]->(p:Product)
WITH c,
     sum(p.price) AS totalSpent
  ORDER BY totalSpent DESC
  SKIP 3
SET c.topSpender = false
RETURN c.firstName AS customer,
       totalSpent,
       c.topSpender AS topSpender
customer totalSpent topSpender

"Leila"

1000

false

"Niko"

760

false

"Hannah"

260

false

"Keisha"

250

false

行:4

ORDER BYLIMITSKIP 也可在 WITH 子句之后使用,以在继续后续模式匹配之前缩小行集。在下面的查询中,首先匹配由 Foodies Inc. 供应的所有产品。WITH 将这些产品向前传递,ORDER BYprice 降序排序,LIMIT 仅保留最昂贵的那一个。第二个 MATCH 子句随后仅从该单一产品进行匹配,以找到所有购买过该产品的客户。

使用排序和分页控制模式匹配作用域
MATCH (:Supplier {name: 'Foodies Inc.'})-[:SUPPLIES]->(p:Product)
WITH p
  ORDER BY p.price DESC
  LIMIT 1
MATCH (p)<-[:BUYS]-(c:Customer)
RETURN p.name AS product,
       p.price AS price,
       collect(c.firstName) AS customers
product 价格 customers

"Coffee"

10

["Mateo", "Hannah", "Niko"]

行:1

过滤结果

WITH 之后可以紧跟 WHERE 子句来过滤结果。类似于用于排序和分页的子句,WHERE 应理解为 WITH 执行的结果操作的一部分——而不是独立子句——在将结果传递给后续子句之前进行处理。更多信息,请参阅 WITH 之后使用 WHERE

使用 WITHWHERE 进行过滤
UNWIND [1, 2, 3, 4, 5, 6] AS x
WITH x
  WHERE x > 2
RETURN x
x

3

4

5

6

行:4

在下面的查询中,WITHWHERE 用于过滤掉 totalSales 小于 1000 的任何 Supplier 节点。注意在 collect() 中使用 DISTINCT 来移除重复的 Customer 节点。

使用 WITHWHERE 过滤属性值
MATCH (s:Supplier)-[:SUPPLIES]->(p:Product)<-[:BUYS]-(c:Customer)
WITH s,
     sum(p.price) AS totalSales,
     count(DISTINCT c) AS uniqueCustomers
  WHERE totalSales > 1000
RETURN s.name AS supplier,
       totalSales,
       uniqueCustomers
supplier totalSales uniqueCustomers

"TechCorp"

5250

7

行:1

FILTER 子句可作为 WITH * WHERE <predicate> 结构的更简洁替代方案。更多信息,请参阅 FILTER 作为 WITH * WHERE 的替代方案