Cypher 中的子查询
回顾我们的示例图谱
我们所有的代码示例都将沿用之前使用过的图谱示例,但在本页的查询中会包含一些更多的数据。下图展示了新的图谱,这是一个由人员、他们所就职的公司以及他们喜欢的技术组成的网络。
我们增加了一个 Person 节点(蓝色),该节点 WORKS_FOR(就职于)一个 Company 节点(红色),并 LIKES(喜欢)一个 Technology(绿色)节点:Ryan 就职于 Company Z 并喜欢 Python。你可以在图谱的右侧找到这些数据。
回顾一下,每个人还可以与其他人拥有多个 IS_FRIENDS_WITH(是朋友)关系。
你可以使用以下 Cypher® 查询来创建此数据集
CREATE (diana:Person {name: "Diana"})-[:LIKES]->(query:Technology {type: "Query Languages"})
CREATE (melissa:Person {name: "Melissa", twitter: "@melissa"})-[:LIKES]->(query)
CREATE (dan:Person {name: "Dan", twitter: "@dan", yearsExperience: 6})-[:LIKES]->(etl:Technology {type: "Data ETL"})<-[:LIKES]-(melissa)
CREATE (xyz:Company {name: "XYZ"})<-[:WORKS_FOR]-(sally:Person {name: "Sally", yearsExperience: 4})-[:LIKES]->(integrations:Technology {type: "Integrations"})<-[:LIKES]-(dan)
CREATE (sally)<-[:IS_FRIENDS_WITH]-(john:Person {name: "John", yearsExperience: 5, birthdate: "1985-04-04"})-[:LIKES]->(java:Technology {type: "Java"})
CREATE (john)<-[:IS_FRIENDS_WITH]-(jennifer:Person {name: "Jennifer", twitter: "@jennifer", yearsExperience: 5, birthdate: "1988-01-01"})-[:LIKES]->(java)
CREATE (john)-[:WORKS_FOR]->(xyz)
CREATE (sally)<-[:IS_FRIENDS_WITH]-(jennifer)-[:IS_FRIENDS_WITH]->(melissa)
CREATE (joe:Person {name: "Joe", birthdate: "1988-08-08"})-[:LIKES]->(query)
CREATE (mark:Person {name: "Mark", twitter: "@mark"})
CREATE (ann:Person {name: "Ann"})
CREATE (x:Company {name: "Company X"})<-[:WORKS_FOR]-(diana)<-[:IS_FRIENDS_WITH]-(joe)-[:IS_FRIENDS_WITH]->(mark)-[:LIKES]->(graphs:Technology {type: "Graphs"})<-[:LIKES]-(jennifer)-[:WORKS_FOR]->(:Company {name: "Neo4j"})
CREATE (ann)<-[:IS_FRIENDS_WITH]-(jennifer)-[:IS_FRIENDS_WITH]->(mark)
CREATE (john)-[:LIKES]->(:Technology {type: "Application Development"})<-[:LIKES]-(ann)-[:IS_FRIENDS_WITH]->(dan)-[:WORKS_FOR]->(abc:Company {name: "ABC"})
CREATE (ann)-[:WORKS_FOR]->(abc)
CREATE (a:Company {name: "Company A"})<-[:WORKS_FOR]-(melissa)-[:LIKES]->(graphs)<-[:LIKES]-(diana)
CREATE (:Technology {type: "Python"})<-[:LIKES]-(:Person {name: "Ryan"})-[:WORKS_FOR]->(:Company {name: "Company Z"})
子查询简介
子查询是在 Neo4j 4.0 中引入的。
有关如何使用它们的详细信息,请前往 Cypher 手册 → 子查询。
Neo4j 中可以使用以下类型的子查询
本节将介绍 EXISTS、COUNT 和 CALL {…} 子查询。
要了解有关使用 CALL {…} IN TRANSACTIONS 的更多信息,请参阅以下关于如何将 CSV 数据导入 Neo4j 数据库的教程中的代码示例
COLLECT 子查询是在 Neo4j 5.6 中引入的。这是一种新型子查询,用于将子查询的结果收集到列表中,以便可以执行诸如 DISTINCT、ORDER BY、LIMIT 和 SKIP 等后续操作。COLLECT 子查询与 COUNT 和 EXISTS 子查询的不同之处在于,其末尾的 RETURN 子句是强制性的。COLLECT 子查询中的 RETURN 子句必须恰好返回一列。
Cypher 子查询
子查询是一组在其自身作用域内执行的 Cypher 语句。子查询通常由外部包围查询调用。
以下是关于子查询需要了解的一些重要事项
-
子查询返回由
RETURN子句中的变量引用的值。 -
子查询不能返回与外部查询中使用的变量同名的变量。
-
必须明确地将变量从外部查询传递给子查询。
子查询由大括号 ({ }) 划定范围。
在获取正确结果一章的 基于模式进行过滤 一节中,你已经学习了如何基于模式进行过滤。例如,你可以编写以下查询来查找在 Neo4j 工作的人的朋友
MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person)
WHERE exists((p)-[:WORKS_FOR]->(:Company {name: 'Neo4j'}))
RETURN p, r, friend
如果你在 Neo4j Browser 中运行此查询,将返回以下图谱
Cypher 子查询 可以实现更强大的模式过滤。你可以使用 EXISTS 子查询,而不是在 WHERE 子句中使用 exists 函数。你可以用以下查询重现之前的示例
MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person)
WHERE EXISTS {
MATCH (p)-[:WORKS_FOR]->(:Company {name: 'Neo4j'})
}
RETURN p, r, friend
你将得到相同的结果,这很好,但到目前为止,你只是用更多的代码实现了同样的事情!
接下来,让我们编写一个比仅使用 WHERE 子句或 exists 函数更强大的过滤子查询。
假设
-
你想要找到那些在名称以 'Company' 开头的公司工作,并且喜欢至少一种被三个人或更多人喜欢的技术的人。
-
你并不关心具体是哪些技术。
你可能会尝试用以下查询来回答这个问题
MATCH (person:Person)-[:WORKS_FOR]->(company)
WHERE company.name STARTS WITH "Company"
AND (person)-[:LIKES]->(t:Technology)
AND COUNT { (t)<-[:LIKES]-() } >= 3
RETURN person.name as person, company.name AS company;
如果你运行此查询,将看到以下输出
Variable `t` not defined (line 4, column 25 (offset: 112))
"AND (person)-[:LIKES]->(t:Technology)"
^
你可以找到喜欢某项技术的人,但无法检查是否至少有三个人也喜欢该技术,因为变量 t 不在 WHERE 子句的作用域内。让我们将两个 AND 语句移入 EXISTS 子查询块中,得到以下查询
MATCH (person:Person)-[:WORKS_FOR]->(company)
WHERE company.name STARTS WITH "Company"
AND EXISTS {
MATCH (person)-[:LIKES]->(t:Technology)
WHERE COUNT { (t)<-[:LIKES]-() } >= 3
}
RETURN person.name as person, company.name AS company;
现在你可以成功运行该查询,返回以下结果
| person | company |
|---|---|
"Melissa" |
"CompanyA" |
"Diana" |
"CompanyX" |
如果你回想本指南开头的图谱可视化,Ryan 是唯一另一个在名称以 'Company' 开头的公司工作的人。他在此查询中被过滤掉了,因为他唯一喜欢的 Technology 是 Python,且并没有另外三个人喜欢 Python。
结果返回型子查询
到目前为止,你已经学习了如何使用子查询来过滤结果,但这并没有完全展示它们的强大之处。你还可以使用子查询来返回结果。
假设你想编写一个查询,找到喜欢 Java 或拥有多个朋友的人。此外,你希望返回按出生日期降序排列的结果。这可以通过使用 UNION 子句和 COUNT 子查询部分实现
MATCH (p:Person)-[:LIKES]->(:Technology {type: "Java"})
RETURN p.name AS person, p.birthdate AS dob
ORDER BY dob DESC
UNION
MATCH (p:Person)
WHERE COUNT { (p)-[:IS_FRIENDS_WITH]->() } > 1
RETURN p.name AS person, p.birthdate AS dob
ORDER BY dob DESC;
如果你运行该查询,会看到以下输出
| person | dob |
|---|---|
"Jennifer" |
1988-01-01 |
"John" |
1985-04-04 |
"Joe" |
1988-08-08 |
你得到了正确的人。但是 UNION 方法只允许我们对每个 UNION 子句进行排序,而不是针对所有行进行排序。
你可以尝试另一种方法,分别执行每个子查询,并使用 collect() 函数从每个部分收集人员。有些人既喜欢 Java 又拥有多个朋友,因此你需要使用 RETURN 子句中的 DISTINCT 操作符来去除重复项
// Find people who like Java
MATCH (p:Person)-[:LIKES]->(:Technology {type: "Java"})
WITH collect(p) AS peopleWhoLikeJava
// Find people with more than one friend
MATCH (p:Person)
WHERE COUNT { (p)-[:IS_FRIENDS_WITH]->() } > 1
WITH collect(p) AS popularPeople, peopleWhoLikeJava
WITH popularPeople + peopleWhoLikeJava AS people
// Unpack the collection of people and order by birthdate
UNWIND people AS p
RETURN DISTINCT p.name AS person, p.birthdate AS dob
ORDER BY dob DESC
如果你运行该查询,将得到以下输出
| person | dob |
|---|---|
"Joe" |
1988-08-08 |
"Jennifer" |
1988-01-01 |
"John" |
1985-04-04 |
这种方法有效,但编写起来比较困难,因为你必须不断地将查询的部分传递给下一部分。
CALL {…} 子句为你提供了两全其美的方案
-
你可以使用
UNION方法来运行单独的查询并去除重复项。 -
你可以随后对结果进行排序。
我们使用 CALL {…} 子句的查询如下所示
CALL {
MATCH (p:Person)-[:LIKES]->(:Technology {type: "Java"})
RETURN p
UNION
MATCH (p:Person)
WHERE COUNT { (p)-[:IS_FRIENDS_WITH]->() } > 1
RETURN p
}
RETURN p.name AS person, p.birthdate AS dob
ORDER BY dob DESC;
如果你运行该查询,将得到以下输出
| person | dob |
|---|---|
"Joe" |
1988-08-08 |
"Jennifer" |
1988-01-01 |
"John" |
1985-04-04 |
你可以进一步扩展查询,返回这些人喜欢的技术以及他们拥有的朋友。以下查询展示了如何做到这一点
CALL {
MATCH (p:Person)-[:LIKES]->(:Technology {type: "Java"})
RETURN p
UNION
MATCH (p:Person)
WHERE COUNT { (p)-[:IS_FRIENDS_WITH]->() } > 1
RETURN p
}
WITH p,
[(p)-[:LIKES]->(t) | t.type] AS technologies,
[(p)-[:IS_FRIENDS_WITH]->(f) | f.name] AS friends
RETURN p.name AS person, p.birthdate AS dob, technologies, friends
ORDER BY dob DESC;
| person | dob | technologies | friends |
|---|---|---|---|
"Joe" |
1988-08-08 |
["Query Languages"] |
["Mark", "Diana"] |
"Jennifer" |
1988-01-01 |
["Graphs", "Java"] |
["Sally", "Mark", "John", "Ann", "Melissa"] |
"John" |
1985-04-04 |
["Java", "Application Development"] |
["Sally"] |
你还可以将聚合函数应用于子查询的结果。以下查询返回喜欢 Java 或拥有多个朋友的人中最年轻和最年长的人。
CALL {
MATCH (p:Person)-[:LIKES]->(:Technology {type: "Java"})
RETURN p
UNION
MATCH (p:Person)
WHERE COUNT { (p)-[:IS_FRIENDS_WITH]->() } > 1
RETURN p
}
RETURN min(p.birthdate) AS oldest, max(p.birthdate) AS youngest
| oldest | youngest |
|---|---|
1985-04-04 |
1988-08-08 |