优化查询结果

本指南介绍了如何通过筛选、排序、比较、别名等方式来细化 Cypher® 查询的结果。

示例数据集

创建 免费 Aura 实例 后,点击“Connect”(连接)按钮并选择“Query”(查询)。在 Cypher 编辑器中,复制并粘贴以下 Cypher 语句并执行查询。

CREATE (matrix:Movie {title: 'The Matrix', released: 1997})
CREATE (cloudAtlas:Movie {title: 'Cloud Atlas', released: 2012})
CREATE (forrestGump:Movie {title: 'Forrest Gump', released: 1994})
CREATE (larryCrowne:Movie {title: 'Larry Crowne', released: 2011})
CREATE (keanu:Person {name: 'Keanu Reeves', born: 1964})
CREATE (robert:Person {name: 'Robert Zemeckis', born: 1951})
CREATE (tom:Person {name: 'Tom Hanks', born: 1956})
CREATE (tom)-[:ACTED_IN {roles: ['Forrest']}]->(forrestGump)
CREATE (tom)-[:ACTED_IN {roles: ['Zachry']}]->(cloudAtlas)
CREATE (tom)-[:ACTED_IN {roles: ['Larry Crowne']}]->(larryCrowne)
CREATE (tom)-[:DIRECTED]->(larryCrowne)
CREATE (robert)-[:DIRECTED]->(forrestGump)

CREATE (diana:Person {name: "Diana"})
CREATE (melissa:Person {name: "Melissa", twitter: "@melissa"})
CREATE (dan:Person {name: "Dan", twitter: "@dan", yearsExperience: 6})
CREATE (sally:Person {name: "Sally", yearsExperience: 4})
CREATE (john:Person {name: "John", yearsExperience: 5})
CREATE (jennifer:Person {name: "Jennifer", twitter: "@jennifer", yearsExperience: 5})
CREATE (joe:Person {name: "Joe"})
CREATE (mark:Person {name: "Mark", twitter: "@mark"})
CREATE (ann:Person {name: "Ann"})
CREATE (xyz:Company {name: "XYZ"})
CREATE (x:Company {name: "Company X"})
CREATE (a:Company {name: "Company A"})
CREATE (Neo4j:Company {name: "Neo4j"})
CREATE (abc:Company {name: "ABC"})
CREATE (query:Technology {type: "Query Languages"})
CREATE (etl:Technology {type: "Data ETL"})
CREATE (integrations:Technology {type: "Integrations"})
CREATE (graphs:Technology {type: "Graphs"})
CREATE (dev:Technology {type: "Application Development"})
CREATE (java:Technology {type: "Java"})
CREATE (diana)-[:LIKES]->(query)
CREATE (melissa)-[:LIKES]->(query)
CREATE (dan)-[:LIKES]->(etl)<-[:LIKES]-(melissa)
CREATE (xyz)<-[:WORKS_FOR]-(sally)-[:LIKES]->(integrations)<-[:LIKES]-(dan)
CREATE (sally)<-[:IS_FRIENDS_WITH]-(john)-[:LIKES]->(java)
CREATE (john)<-[:IS_FRIENDS_WITH]-(jennifer)-[:LIKES]->(java)
CREATE (john)-[:WORKS_FOR]->(xyz)
CREATE (sally)<-[:IS_FRIENDS_WITH]-(jennifer)-[:IS_FRIENDS_WITH]->(melissa)
CREATE (joe)-[:LIKES]->(query)
CREATE (x)<-[:WORKS_FOR]-(diana)<-[:IS_FRIENDS_WITH]-(joe)-[:IS_FRIENDS_WITH]->(mark)-[:LIKES]->(graphs)<-[:LIKES]-(jennifer)-[:WORKS_FOR {startYear: 2017}]->(Neo4j)
CREATE (ann)<-[:IS_FRIENDS_WITH]-(jennifer)-[:IS_FRIENDS_WITH]->(mark)
CREATE (john)-[:LIKES]->(dev)<-[:LIKES]-(ann)-[:IS_FRIENDS_WITH]->(dan)-[:WORKS_FOR]->(abc)
CREATE (ann)-[:WORKS_FOR]->(abc)
CREATE (a)<-[:WORKS_FOR]-(melissa)-[:LIKES]->(graphs)<-[:LIKES]-(diana)

筛选 (Filter)

您可以使用 WHERE 子句或 FILTER 子句(仅在 Cypher 25 中可用)来筛选结果,仅返回数据的一个子集。

属性

您可以根据属性来筛选结果。例如,要查找示例数据集中哪些人拥有 Twitter 账号,可以使用以下查询:

MATCH (p:Person)
RETURN p.name, p.twitter

由于您 MATCH所有 Person 节点,无论它们是否有 twitter 属性,它们都会被返回。在图中的 12 个 Person 节点中,只有 4 个(Melissa、Dan、Jennifer、Mark)包含 twitter 属性的值。因为查询匹配并返回了所有 Person 节点,所以您会得到另外 8 个 null 结果。

在 Neo4j 中,属性仅在有值时才存在(存储)。null 属性值不会被存储。这确保了您的节点和关系只保留有价值、必要的信息。

要仅返回包含值的那些结果,您可以使用 WHERE 子句并指定仅返回非 null 的结果:

MATCH (p:Person)
WHERE p.twitter IS NOT NULL
RETURN p.name, p.twitter

或者,在 Cypher 25 中使用 FILTER

CYPHER 25
MATCH (p:Person)
FILTER p.twitter IS NOT NULL
RETURN p.name, p.twitter

如果您使用的是其他版本的 Cypher,则需要在查询的第一行添加 CYPHER 25。这不会更改默认的语言版本,它只是确保该查询在 Cypher 25 中运行。否则,仅属于此版本的 FILTER 子句将无法工作。

要查看 FILTERWHERE 之间的比较,请参考 Cypher → FILTER

结果为:

p.name p.twitter

"Melissa"

"@melissa"

"Dan"

"@dan"

"Jennifer"

"@jennifer"

“Mark”

"@mark"

行:4

字符串和部分值

您可以使用特定的 谓词 (predicates) 配合 WHERE,根据存储在节点或关系中的部分值来筛选结果。

STARTS WITH

STARTS WITH 运算符搜索以您指定的字符串开头的属性值,例如,以字母“M”开头的名称。

MATCH (p:Person)
WHERE p.name STARTS WITH 'M'
RETURN p.name;

结果为:

p.name

"Melissa"

“Mark”

行:2

CONTAINS

CONTAINS 运算符检查指定的字符串是否为属性值的一部分,例如,是否包含字母“a”。

MATCH (p:Person)
WHERE p.name CONTAINS 'a'
RETURN p.name;

结果为:

p.name

"Keanu Reeves"

"Tom Hanks"

"Diana"

"Melissa"

"Dan"

"Sally"

“Mark”

行:7

ENDS_WITH

ENDS_WITH 运算符搜索属性值末尾的指定字符串,例如,以“n”结尾的 name

MATCH (p:Person)
WHERE p.name ENDS WITH 'n'
RETURN p.name;

结果为:

p.name

"Dan"

"John"

"Ann"

行:3

或者,您也可以在不使用字符串运算符的情况下,使用正则表达式来查找属性值。例如,如果您想查找所有包含“Jo”的名称,同时保持大小写敏感(查询默认是区分大小写的):

MATCH (p:Person)
WHERE p.name =~ 'Jo.*'
RETURN p.name
p.name

"John"

"Joe"

行:2

如果您不希望查询区分大小写,可以使用 toLower() 配合 CONTAINS

MATCH (p:Person)
WHERE toLower(p.name) CONTAINS "jo"
RETURN p.name

或者,使用 toUpper()

MATCH (p:Person)
WHERE toUpper(p.name) CONTAINS "JO"
RETURN p.name

这两个选项都会返回所有包含“jo”的 name 属性值,而忽略大小写。

IN

您可以使用 IN 运算符来测试属性是否存在于给定的值列表中。例如,检查图中是否有任何 Person 节点的 yearsExperience 值为“1”、“5”或“6”:

MATCH (p:Person)
WHERE p.yearsExperience IN [1, 5, 6]
RETURN p.name, p.yearsExperience

结果显示没有人有 1 年的经验,但有三个人有 5 或 6 年经验。

p.name p.yearsExperience

"Dan"

5

"John"

6

"Jennifer"

5

行:3

模式 (Patterns)

例如,要查找与在 Neo4j 工作的人是朋友关系的人,您可以筛选是否存在从友谊中的 p:Person 指向 (:Company {name:'Neo4j'})[:WORKS_FOR] 关系。

MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person)
WHERE exists((p)-[:WORKS_FOR]->(:Company {name: 'Neo4j'}))
RETURN p, r, friend;

得到的结果如下:

另外,您还可以通过从结果中排除某些模式来筛选结果:例如,如果您想查找 Jennifer 的朋友中哪些人没有在任何公司工作,请使用以下查询:

MATCH (p:Person)-[r:IS_FRIENDS_WITH]->(friend:Person)
WHERE p.name = 'Jennifer'
AND NOT exists((friend)-[:WORKS_FOR]->(:Company))
RETURN friend.name;

您只会得到“Mark”作为结果。

OPTIONAL 模式

有时,即使结果不符合所有条件,您也希望从查询中返回结果。为此,可以使用 OPTIONAL MATCH,它会在可选子句不匹配时返回空值 (null)。

例如,如果您搜索名字以“J”开头且“可选地”在某家公司工作的人:

MATCH (p:Person)
WHERE p.name STARTS WITH 'J'
OPTIONAL MATCH (p)-[:WORKS_FOR]-(other:Company)
RETURN p.name, other.name;

得到的结果如下:

p.name other.name

"Jennifer"

"Neo4j"

"John"

"XYZ"

"Joe"

null

行:3

请注意,Joe 被返回了,因为他的名字以字母“J”开头,但他的公司名称为 null。这是因为他没有指向 COMPANY 节点的 WORKS_FOR 关系。由于您使用了 OPTIONAL MATCH,他的 Person 节点仍会从第一个匹配项中返回,但未找到可选匹配项,因此 other.name 返回 null 结果。

要查看差异,尝试在第二个匹配项前去掉 OPTIONAL 运行查询。Joe 的行将不再出现在结果中。这是因为 Cypher 会将该语句读取为 AND 匹配,因此该人必须同时满足第一个条件(名字以“J”开头)和第二个条件(该人在某家公司工作)。

更复杂的模式

您可以查询具有多个关系的模式。例如,如果您想知道谁在同一家公司工作且也是朋友:

MATCH (p1:Person)-[:WORKS_FOR]->(company:Company)<-[:WORKS_FOR]-(p2:Person),
(p1)-[:IS_FRIENDS_WITH]->(p2)
RETURN p1.name AS people1, p2.name AS people2, company.name AS company

结果为:

people1 people2 company

"John"

"Sally"

"XYZ"

"Ann"

"Dan"

"ABC"

行:2

请注意,查询的第一行末尾有一个逗号,下一行添加了另一个 MATCH 模式。这允许您将不同的模式链接在一起,类似于之前使用的 WHERE exists(<pattern>)

利用这种结构,您可以添加多种不同的模式并将它们链接起来,从而通过特定模式遍历图的各个部分。

有关如何查询模式的更多信息,请参阅 Cypher → Patterns

比较值

相等性

您可以使用比较来筛选值,首先是相等性:

MATCH (m:Movie)
WHERE m.title = 'The Matrix'
RETURN m

结果为:

m

(:Movie {title: "The Matrix", released: 1997})

行:1

数值

另一个选项是数值比较,以确认列表中值的存在。

以下示例中的 WHERE 子句包含大于比较,用于查找 2000 年之后发行的电影:

MATCH (m:Movie)
WHERE m.released > 2000
RETURN m.title, m.released

结果为:

m m.release

"Cloud Atlas"

2012

"Larry Crowne"

2011

行:2

Cypher 拥有其他数学运算符,允许进行其他类型的数值比较。请参阅 Cypher → Mathematical operators 获取完整的运算符列表和示例。

布尔值

Cypher 的布尔运算符允许您组合或评估逻辑条件。

例如,查找图中谁导演了电影但没有参演:

MATCH (p:Person)-[:DIRECTED]->(m)
WHERE NOT (p)-[:ACTED_IN]->()
RETURN p, m

结果为:

这是因为查询查找的是通过 DIRECTED 关系连接到 Movie 节点,而没有 ACTED_IN 关系的 Person 节点。如果不再限制 ACTED_IN 关系:

MATCH (p:Person)-[:DIRECTED]->(m)
RETURN p, m

那么结果中会包含 Tom Hanks 和电影“Larry Crowne”,因为 Tom Hanks 导演参演了该电影。

请参阅 Cypher → Boolean operators 获取完整的运算符列表和示例。

值范围

您可以查询特定数字或日期范围内的数据,例如,查找特定时间线内的事件、年龄值等。

如果您想知道谁的经验在三到七年之间:

MATCH (p:Person)
WHERE 3 <= p.yearsExperience <= 7
RETURN p

结果为:

别名结果

默认情况下,Cypher 返回标签作为表格结果中的列名。但是,您可以使用 AS 使用别名使结果更易于理解。

例如,如果您想让表格结果的列标题显示为 name 而不是标签 p.name

MATCH (p:Person)
RETURN p.name AS name

结果为:

名称 (name)

"Keanu Reeves"

"Robert Zemeckis"

"Tom Hanks"

"Diana"

"Melissa"

"Dan"

"Sally"

"John"

"Jennifer"

"Joe"

“Mark”

"Ann"

Rows: 12

避免重复

例如,要查找哪些人通过 IS_FRIENDS_WITH 关系连接:

MATCH (p:Person)-[:IS_FRIENDS_WITH]-(friend:Person)
RETURN friend.name;

结果为:

friend.name

"Joe"

"Jennifer"

"Ann"

"John"

"Jennifer"

"Sally"

"Jennifer"

"Melissa"

"Sally"

"John"

“Mark”

"Ann"

"Diana"

“Mark”

"Jennifer"

"Joe"

"Dan"

"Jennifer"

Rows: 18

请注意,有些名字出现了不止一次。例如,“Sally”出现了两次,因为她通过两个 IS_FRIENDS_WITH 关系连接了两个人。要避免这种情况,在 RETURN 之后添加 DISTINCT

MATCH (p:Person)-[:IS_FRIENDS_WITH]->(friend:Person)
RETURN DISTINCT friend.name;

现在结果如下:

friend.name

"Sally"

"Melissa"

"John"

“Mark”

"Ann"

"Diana"

"Dan"

行:7

排序

如果您希望对结果进行排序,可以在 RETURN 子句之后使用 ORDER BY 子句。例如,按降序列出每个人拥有的经验年数:

MATCH (p:Person)
RETURN p.yearsExperience AS yearsExperience
ORDER BY yearsExperience DESC

结果如下:

yearsExperience

null

null

null

null

null

null

null

null

6

5

5

4

Rows: 12

请注意,前 8 行的结果包含 null。这意味着并非所有返回的 Person 节点都有 yearsExperience 属性。

由于在降序排序时 null 被视为可能的最大值,因此它们会排在数值之前。如果是升序排序,null 值将出现在最后。

(可选)您可以使用 WHEREIS NOT NULL 来避免返回 null 值:

MATCH (p:Person)
WHERE p.yearsExperience IS NOT NULL
RETURN p.yearsExperience AS yearsExperience
ORDER BY yearsExperience DESC

限制 (Limit)

当您想要限制返回结果的数量时,可以使用 LIMIT。例如,当前的示例图中共有 12 个 Person 节点,如果您只想检索其中的 3 个:

MATCH (p:Person)
RETURN p
LIMIT 3

结果为:

p

(:Person {born: 1964, name: "Keanu Reeves"})

(:Person {born: 1951, name: "Robert Zemeckis"})

(:Person {born: 1956, name: "Tom Hanks"})

行:3

由于您没有使用 ORDER BY 来指定排序方式,查询会检索添加到图中的前三个 Person 节点。要获取随机结果,可以在查询中添加 ORDER BY rand()

MATCH (p:Person)
RETURN p
ORDER BY rand()
LIMIT 3

现在,每次运行此查询时,您都会得到 3 个随机的 Person 节点。

聚合

Cypher 提供了几种 聚合函数 (aggregating functions),让您可以从一组值中计算出一个单一的值。

您可以将它们与 RETURNWITH 子句一起使用来聚合结果。

函数 描述

avg()

返回一组 INTEGERFLOATDURATION 值的平均值。

collect()

返回一个包含表达式所返回值的列表。

count()

返回值的数量或行数。

max()

返回一组值中的最大值。

min()

返回一组值中的最小值。

percentileCont()

使用线性插值法返回一组值中指定百分位数的数值。

percentileDisc()

使用舍入法返回一组值中与指定百分位数最接近的 INTEGERFLOAT 值。

stDev()

返回给定值在样本总体中的标准差。

stDevP()

返回给定值在整个总体中的标准差。

sum()

返回一组 INTEGER、FLOAT 或 DURATION 值的总和。

请参阅 Cypher → Aggregating 获取所有聚合函数的完整列表和示例。

解构 (Unwind)

如果您有一个列表想要检查或拆分其中的值,Cypher 提供了 UNWIND 函数来实现。

例如,如果您想查看示例图中哪些人喜欢“Graphs”和/或“Query Languages”:

WITH ['Graphs','Query Languages'] AS likedTech
UNWIND likedTech AS technology
MATCH (p:Person)-[r:LIKES]-(t:Technology {type: technology})
RETURN t.type, p.name AS people;

结果为:

t.type people

"Graphs"

"Diana"

"Graphs"

"Melissa"

"Graphs"

"Jennifer"

"Graphs"

“Mark”

"Query Languages"

"Diana"

"Query Languages"

"Melissa"

"Query Languages"

"Joe"

行:7

您可以使用 collect() 函数将结果分组。如果您这样重写查询:

WITH ['Graphs','Query Languages'] AS likedTech
UNWIND likedTech AS technology
MATCH (p:Person)-[r:LIKES]-(t:Technology {type: technology})
RETURN t.type, collect(p.name) AS people;

结果看起来会更整洁:

t.type people

"Graphs"

["Diana", "Melissa", "Jennifer", "Mark"]

"Query Languages"

["Diana", "Melissa", "Joe"]

行:2

条目数量

如果您有一个元素列表,也可以使用 size() 函数返回该列表中的元素数量。

例如,如果您想知道图中每个人有多少个朋友:

MATCH (p:Person)-[:IS_FRIENDS_WITH]-(friend:Person)
RETURN p.name, size(collect(friend.name)) AS numberOfFriends;

请注意,collect() 函数是在 size() 函数内部使用的。如果您不使用它(即只写 size(friend.name)),函数的作用将是计算 friend.name 属性字符串值中包含多少个字符。因为您想知道每个人有多少个朋友,所以 collect() 作为聚合函数收集所有的 friend.name 值,而 size() 则返回总数量。

结果为:

p.name numberOfFriends

"Diana"

1

"Melissa"

1

"Dan"

1

"Sally"

2

"John"

2

"Jennifer"

5

"Joe"

2

“Mark”

2

"Ann"

1

行:9

另一点需要注意的是,如果您在关系中添加了方向:

MATCH (p:Person)-[:IS_FRIENDS_WITH]->(friend:Person)
RETURN p.name, size(collect(friend.name)) AS numberOfFriends;

您会得到不同的结果:

p.name numberOfFriends

"John"

1

"Jennifer"

5

"Joe"

2

"Ann"

1

Rows:4

这是因为 IS_FRIENDS_WITH 关系是有向的。

例如,Jennifer 和 Mark 通过从 Jennifer 到 Mark 的出站 IS_FRIENDS_WITH 关系连接(即 (jennifer)-[:IS_FRIENDS_WITH]→(mark)),而不是 (mark)-[:IS_FRIENDS_WITH]→(jennifer)

在实际应用中,Jennifer 和 Mark 是朋友这一点才是最重要的,因此关系的方向并不重要。但是,将此信息添加到图中时,关系 必须具有方向。另一方面,查询它并不一定需要方向。

创建方向相反的新模式(即 (mark)-[:IS_FRIENDS_WITH]→(jennifer))从语法角度看并没有错,但如果方向对于回答“谁与谁是朋友?”这个问题无关紧要,这就是重复的信息。这就是为什么使用无向关系查询可以得到更准确的结果。

持续学习

Cypher 有许多其他资源可以细化查询结果。如果您想继续学习,请参阅以下页面:

  1. 组合查询 → 查看如何使用 UNIONWHENNEXT 来合并查询、创建线性组合以及不同的查询分支。

  2. SKIP → 定义从输出结果的第几行开始包含。

  3. 表达式 → 可用于评估值的运算符和表达式。

  4. 函数 → Cypher 函数的完整列表。

  5. 索引 → 加速数据检索的策略。