1. 什么是查询过滤 #
在实际应用中,我们通常不需要查询图中的所有数据,而是需要根据特定条件筛选出符合要求的数据。这个过程就叫做查询过滤。
1.1 为什么需要过滤? #
想象一下,如果你要在一个包含成千上万人物的社交网络图中查找"所有年龄在25-30岁之间、居住在北京市、喜欢编程的人",你肯定不希望返回所有人的信息,而是只返回符合这些条件的人。这就是过滤的作用。
1.2 过滤的方式 #
在Cypher中,主要有两种方式来实现过滤:
- 在MATCH中直接指定条件:适合简单的、单一的过滤条件
- 使用WHERE子句:适合复杂的、需要组合多个条件的过滤
1.3 前置知识:WHERE子句 #
WHERE子句是Cypher中用于过滤查询结果的关键字,类似于SQL中的WHERE子句。它的作用是:
- 在MATCH匹配到数据后,进一步筛选出符合条件的数据
- 支持多种比较操作符(=, <>, >, <, >=, <=)
- 支持逻辑操作符(AND, OR, NOT)
- 支持字符串匹配、范围查询等高级功能
2. WHERE子句基础 #
WHERE子句是Cypher中最重要的过滤工具。它允许你在查询中添加条件,只返回满足这些条件的数据。
2.1 WHERE子句的基本语法 #
WHERE子句通常紧跟在MATCH子句之后,在RETURN子句之前:
MATCH (节点)
WHERE 条件
RETURN 结果2.2 WHERE子句的作用 #
WHERE子句就像一个"筛选器",它会检查每一行匹配到的数据,只保留满足条件的数据行。
3. 基本过滤条件 #
基本过滤是最常用的过滤方式,包括相等比较、不等比较等。
3.1 相等比较 #
查找属性值等于某个特定值的节点。
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三", age: 30, city: "北京"}),
(p2:Person {name: "李四", age: 25, city: "上海"}),
(p3:Person {name: "王五", age: 30, city: "北京"}),
(p4:Person {name: "赵六", age: 35, city: "广州"})
// 查找名字为"张三"的人
MATCH (p:Person)
WHERE p.name = "张三"
RETURN p
// 查找年龄等于30岁的人
MATCH (p:Person)
WHERE p.age = 30
RETURN p.name, p.age
// 查找城市为"北京"的人
MATCH (p:Person)
WHERE p.city = "北京"
RETURN p.name, p.city3.2 不等比较 #
查找属性值不等于某个特定值的节点。
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三", age: 30, city: "北京"}),
(p2:Person {name: "李四", age: 25, city: "上海"}),
(p3:Person {name: "王五", age: 30, city: "北京"}),
(p4:Person {name: "赵六", age: 35, city: "广州"})
// 查找名字不等于"张三"的人
MATCH (p:Person)
WHERE p.name <> "张三"
RETURN p.name
// 查找年龄不等于30岁的人
MATCH (p:Person)
WHERE p.age <> 30
RETURN p.name, p.age
// 查找城市不是"北京"的人
MATCH (p:Person)
WHERE p.city <> "北京"
RETURN p.name, p.city3.3 逻辑操作符(AND, OR, NOT) #
可以组合多个条件进行更复杂的过滤。
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三", age: 30, city: "北京"}),
(p2:Person {name: "李四", age: 25, city: "上海"}),
(p3:Person {name: "王五", age: 20, city: "北京"}),
(p4:Person {name: "赵六", age: 35, city: "广州"}),
(p5:Person {name: "孙七", age: 28, city: "深圳"})
// 使用 AND:查找年龄大于25岁且小于35岁的人
MATCH (p:Person)
WHERE p.age > 25 AND p.age < 35
RETURN p.name, p.age
// 使用 OR:查找城市为"北京"或"上海"的人
MATCH (p:Person)
WHERE p.city = "北京" OR p.city = "上海"
RETURN p.name, p.city
// 使用 NOT:查找年龄不小于18岁的人(即年龄大于等于18岁)
MATCH (p:Person)
WHERE NOT p.age < 18
RETURN p.name, p.age
// 组合使用:查找(年龄大于25岁且小于35岁)或(城市为"北京")的人
MATCH (p:Person)
WHERE (p.age > 25 AND p.age < 35) OR p.city = "北京"
RETURN p.name, p.age, p.city4. 范围查询 #
范围查询用于查找属性值在某个范围内的节点,比如查找年龄在某个区间的人,或者查找某个时间段内上映的电影。
4.1 前置知识:范围查询 #
范围查询就是查找数值在某个区间内的数据。比如:
- 查找年龄在18-65岁之间的人
- 查找价格在100-500元之间的商品
- 查找在2020-2023年之间创建的数据
4.2 范围查询的语法 #
Cypher支持多种方式表示范围:
- 链式比较:
3 <= age <= 7(最直观) - AND组合:
age >= 3 AND age <= 7(最灵活) - BETWEEN关键字:
age BETWEEN 3 AND 7(最简洁)
4.3 数值范围查询 #
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三", age: 30}),
(p2:Person {name: "李四", age: 25}),
(p3:Person {name: "王五", age: 35}),
(p4:Person {name: "赵六", age: 20}),
(product1:Product {name: "书包", price: 150}),
(product2:Product {name: "水杯", price: 50}),
(product3:Product {name: "运动鞋", price: 300}),
(m1:Movie {title: "长津湖", rating: 9.5}),
(m2:Movie {title: "满江红", rating: 8.5}),
(m3:Movie {title: "功夫", rating: 7.5})
// 方式1:使用链式比较(最直观)
MATCH (p:Person)
WHERE 25 <= p.age <= 35
RETURN p.name, p.age
// 方式2:使用 AND 组合(最灵活)
MATCH (p:Person)
WHERE p.age >= 25 AND p.age <= 35
RETURN p.name, p.age
// 方式3:使用 BETWEEN 关键字(最简洁)
MATCH (p:Person)
WHERE p.age BETWEEN 25 AND 35
RETURN p.name, p.age
// 查找价格在100到500之间的商品
MATCH (product:Product)
WHERE product.price >= 100 AND product.price <= 500
RETURN product.name, product.price
// 查找评分在8.0到10.0之间的电影
MATCH (m:Movie)
WHERE m.rating BETWEEN 8.0 AND 10.0
RETURN m.title, m.rating4.4 日期范围查询 #
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (m1:Movie {title: "长津湖", released: 2021}),
(m2:Movie {title: "满江红", released: 2023}),
(m3:Movie {title: "功夫", released: 2019}),
(p1:Person {name: "张三", created_at: date('2023-06-01')}),
(p2:Person {name: "李四", created_at: date('2022-03-15')}),
(c1:Company {name: "科技公司"}),
(c2:Company {name: "互联网公司"})
CREATE (p1)-[:WORKS_FOR {start_date: date('2021-01-01')}]->(c1),
(p2)-[:WORKS_FOR {start_date: date('2022-06-01')}]->(c2)
// 查找在2020年到2023年之间上映的电影
MATCH (m:Movie)
WHERE m.released >= 2020 AND m.released <= 2023
RETURN m.title, m.released
ORDER BY m.released
// 使用 BETWEEN 查找日期范围
MATCH (m:Movie)
WHERE m.released BETWEEN 2020 AND 2023
RETURN m.title, m.released
// 查找在某个日期之后创建的数据
MATCH (p:Person)
WHERE p.created_at >= date('2023-01-01')
RETURN p.name, p.created_at
// 查找在某个时间段内的工作关系
MATCH (p:Person)-[r:WORKS_FOR]->(c:Company)
WHERE r.start_date >= date('2020-01-01')
AND r.start_date <= date('2023-12-31')
RETURN p.name, c.name, r.start_date5. 属性存在性检查 #
有时候我们需要查找具有某个属性的节点,或者查找没有某个属性的节点。这就是属性存在性检查。
5.1 前置知识:NULL值 #
在数据库中,如果一个属性不存在或者没有被设置,它的值就是NULL(空值)。NULL表示"没有值",与0、空字符串""或False都不同。
5.2 检查属性是否存在 #
使用IS NOT NULL检查属性是否存在,使用IS NULL检查属性是否不存在。
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三", age: 30, email: "zhangsan@example.com"}),
(p2:Person {name: "李四", age: 25, phone: "13800138000"}),
(p3:Person {name: "王五", age: 35}),
(p4:Person {name: "赵六", age: 28, email: "zhaoliu@example.com", phone: "13900139000"}),
(m1:Movie {title: "长津湖", rating: 9.5}),
(m2:Movie {title: "满江红", rating: 8.5, comment: "很好"}),
(m3:Movie {title: "功夫", rating: 8.0})
// 查找有邮箱地址的人(邮箱属性存在且不为空)
MATCH (p:Person)
WHERE p.email IS NOT NULL
RETURN p.name, p.email
// 查找没有邮箱地址的人(邮箱属性不存在或为空)
MATCH (p:Person)
WHERE p.email IS NULL
RETURN p.name
// 查找有电话号码的人
MATCH (p:Person)
WHERE p.phone IS NOT NULL
RETURN p.name, p.phone
// 查找有评分但没有评论的电影
MATCH (m:Movie)
WHERE m.rating IS NOT NULL AND m.comment IS NULL
RETURN m.title, m.rating
// 组合使用:查找年龄大于25岁且有邮箱的人
MATCH (p:Person)
WHERE p.age > 25 AND p.email IS NOT NULL
RETURN p.name, p.age, p.email6. 字符串匹配 #
字符串匹配用于查找属性值符合某种模式的节点,比如查找名字以某个字母开头的人,或者名字包含某个字符串的人。
6.1 前置知识:字符串匹配 #
字符串匹配就是检查一个字符串是否符合某种模式。比如:
- 查找名字以"J"开头的人
- 查找名字包含"an"的人
- 查找名字以"n"结尾的人
6.2 字符串匹配的方式 #
Cypher提供了多种字符串匹配方式:
- STARTS WITH:检查字符串是否以指定前缀开头
- ENDS WITH:检查字符串是否以指定后缀结尾
- CONTAINS:检查字符串是否包含指定子串
- 正则表达式(=~):使用正则表达式进行复杂匹配
6.3 基本字符串匹配 #
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三", email: "zhangsan@gmail.com"}),
(p2:Person {name: "张译", email: "zhangyi@example.com"}),
(p3:Person {name: "李四", email: "lisi@qq.com"}),
(p4:Person {name: "吴京", email: "wujing@gmail.com"}),
(m1:Movie {title: "功夫"}),
(m2:Movie {title: "长津湖"}),
(m3:Movie {title: "满江红"})
// 使用 STARTS WITH:查找名字以"张"开头的人
MATCH (p:Person)
WHERE p.name STARTS WITH "张"
RETURN p.name
// 使用 ENDS WITH:查找名字以"三"结尾的人
MATCH (p:Person)
WHERE p.name ENDS WITH "三"
RETURN p.name
// 使用 CONTAINS:查找名字包含"京"的人
MATCH (p:Person)
WHERE p.name CONTAINS "京"
RETURN p.name
// 查找邮箱以"gmail.com"结尾的人
MATCH (p:Person)
WHERE p.email ENDS WITH "gmail.com"
RETURN p.name, p.email
// 查找标题包含"功夫"的电影
MATCH (m:Movie)
WHERE m.title CONTAINS "功夫"
RETURN m.title
// 组合使用:查找名字以"张"开头且包含"译"的人
MATCH (p:Person)
WHERE p.name STARTS WITH "张" AND p.name CONTAINS "译"
RETURN p.name6.4 正则表达式匹配(高级) #
正则表达式可以用于更复杂的字符串匹配
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三", email: "zhangsan@example.com"}),
(p2:Person {name: "李四", email: "lisi@gmail.com"}),
(p3:Person {name: "王五", email: "wangwu@qq.com"}),
(p4:Person {name: "John", email: "john@example.com"}),
(p5:Person {name: "用户123", email: "user123@test.com"})
// 使用正则表达式:查找名字以"张"或"李"开头的人
// =~ 是正则表达式匹配操作符
MATCH (p:Person)
WHERE p.name =~ "^[张李].*"
RETURN p.name
// 查找邮箱格式正确的人
MATCH (p:Person)
WHERE p.email =~ ".*@.*\\..*"
RETURN p.name, p.email
// 查找名字包含数字的人
MATCH (p:Person)
WHERE p.name =~ ".*[0-9].*"
RETURN p.name
// 查找以大写字母开头的名字
MATCH (p:Person)
WHERE p.name =~ "^[A-Z].*"
RETURN p.name
// 注意:正则表达式中的点号需要转义为 \\. 才能匹配实际的点号7. 模式过滤 #
模式过滤允许你在WHERE子句中使用图模式作为条件。比如查找"有朋友关系但没有工作关系"的人。
7.1 前置知识:模式作为条件 #
在WHERE子句中,你不仅可以检查节点的属性,还可以检查节点之间的关系模式。比如:
- 检查一个人是否有朋友关系
- 检查一个人是否参演过电影
- 检查一个人是否既参演又执导过电影
7.2 检查关系是否存在 #
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三"}),
(p2:Person {name: "李四"}),
(p3:Person {name: "王五"}),
(p4:Person {name: "赵六"}),
(m1:Movie {title: "长津湖"}),
(m2:Movie {title: "满江红"}),
(c1:Company {name: "科技公司"})
CREATE (p1)-[:IS_FRIENDS_WITH]->(p2),
(p2)-[:IS_FRIENDS_WITH]->(p3),
(p1)-[:ACTED_IN]->(m1),
(p1)-[:DIRECTED]->(m2),
(p2)-[:ACTED_IN]->(m1),
(p4)-[:WORKS_FOR]->(c1)
// 查找有朋友关系的人
MATCH (p:Person)
WHERE (p)-[:IS_FRIENDS_WITH]->()
RETURN p.name
// 查找没有朋友关系的人
MATCH (p:Person)
WHERE NOT (p)-[:IS_FRIENDS_WITH]->()
RETURN p.name
// 查找既参演又执导过电影的人
MATCH (p:Person)
WHERE (p)-[:ACTED_IN]->() AND (p)-[:DIRECTED]->()
RETURN p.name
// 查找参演过电影但没有执导过电影的人
MATCH (p:Person)
WHERE (p)-[:ACTED_IN]->() AND NOT (p)-[:DIRECTED]->()
RETURN p.name
// 查找有工作关系但没有朋友关系的人
MATCH (p:Person)
WHERE (p)-[:WORKS_FOR]->() AND NOT (p)-[:IS_FRIENDS_WITH]->()
RETURN p.name8. 可选匹配(OPTIONAL MATCH) #
OPTIONAL MATCH是MATCH的可选版本。即使模式不匹配,也会返回结果(不匹配的部分为null)。
8.1 前置知识:MATCH vs OPTIONAL MATCH #
- MATCH:必须匹配,如果模式不匹配,这一行就不会出现在结果中
- OPTIONAL MATCH:可选匹配,即使模式不匹配,这一行也会出现在结果中,只是不匹配的部分为null
8.2 为什么需要OPTIONAL MATCH? #
有时候我们需要显示所有节点,无论它们是否有某种关系。比如:
- 显示所有人,无论他们是否有工作
- 显示所有电影,无论它们是否有演员
- 显示所有产品,无论它们是否有评论
8.3 基本用法 #
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三"}),
(p2:Person {name: "李四"}),
(p3:Person {name: "王五"}),
(c1:Company {name: "科技公司"}),
(m1:Movie {title: "长津湖"}),
(m2:Movie {title: "满江红"}),
(product1:Product {name: "书包"}),
(product2:Product {name: "水杯"}),
(r1:Review {content: "很好"})
CREATE (p1)-[:WORKS_FOR]->(c1),
(p1)-[:ACTED_IN]->(m1),
(r1)-[:REVIEWED]->(product1)
// 显示所有人,无论他们是否有工作关系
MATCH (p:Person)
OPTIONAL MATCH (p)-[:WORKS_FOR]->(c:Company)
RETURN p.name, c.name AS company
// 显示所有电影,无论它们是否有演员
MATCH (m:Movie)
OPTIONAL MATCH (m)<-[:ACTED_IN]-(a:Person)
RETURN m.title, collect(a.name) AS actors
// 显示所有产品,无论它们是否有评论
MATCH (product:Product)
OPTIONAL MATCH (product)<-[:REVIEWED]-(r:Review)
RETURN product.name, count(r) AS review_count
// 对比:使用 MATCH(必须匹配)
MATCH (p:Person)-[:WORKS_FOR]->(c:Company)
RETURN p.name, c.name
// 只返回有工作关系的人
// 使用 OPTIONAL MATCH(可选匹配)
MATCH (p:Person)
OPTIONAL MATCH (p)-[:WORKS_FOR]->(c:Company)
RETURN p.name, c.name
// 返回所有人,没有工作关系的显示为 null9. 复杂模式查询 #
可以组合多个模式进行更复杂的查询,比如查找"朋友的朋友"、"喜欢相同技术的人"等。
9.1 多跳关系查询 #
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三"}),
(p2:Person {name: "李四"}),
(p3:Person {name: "王五"}),
(p4:Person {name: "赵六"}),
(m1:Movie {title: "长津湖"}),
(m2:Movie {title: "满江红"})
CREATE (p1)-[:IS_FRIENDS_WITH]->(p2),
(p2)-[:IS_FRIENDS_WITH]->(p3),
(p3)-[:IS_FRIENDS_WITH]->(p4),
(p1)-[:ACTED_IN]->(m1),
(p2)-[:ACTED_IN]->(m1),
(p3)-[:ACTED_IN]->(m2),
(p1)-[:DIRECTED]->(m1),
(p4)-[:ACTED_IN]->(m1)
// 查找朋友的朋友(2跳关系)
MATCH (p:Person)-[:IS_FRIENDS_WITH*2]->(friend_of_friend:Person)
RETURN p.name, friend_of_friend.name
// 查找朋友的朋友的朋友(3跳关系)
MATCH (p:Person)-[:IS_FRIENDS_WITH*3]->(fofof:Person)
RETURN p.name, fofof.name
// 查找1到3跳的朋友关系
MATCH (p:Person)-[:IS_FRIENDS_WITH*1..3]->(friend:Person)
RETURN p.name, friend.name
// 查找参演同一部电影的其他演员(通过电影节点连接)
MATCH (a1:Person)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(a2:Person)
WHERE a1 <> a2
RETURN a1.name AS actor1, a2.name AS actor2, m.title AS movie
// 查找导演执导的电影中的演员
MATCH (d:Person)-[:DIRECTED]->(m:Movie)<-[:ACTED_IN]-(a:Person)
RETURN d.name AS director, m.title AS movie, a.name AS actor9.2 查找有共同兴趣的人 #
// 清空数据库
MATCH (n) DETACH DELETE n
// 创建测试数据
CREATE (p1:Person {name: "张三"}),
(p2:Person {name: "李四"}),
(p3:Person {name: "王五"}),
(tech1:Technology {name: "Python"}),
(tech2:Technology {name: "Java"}),
(u1:User {name: "用户1"}),
(u2:User {name: "用户2"}),
(product1:Product {name: "书包"}),
(hobby1:Hobby {name: "编程"}),
(m1:Movie {title: "长津湖"}),
(m2:Movie {title: "满江红"}),
(g1:Genre {name: "动作"})
CREATE (p1)-[:IS_FRIENDS_WITH]->(p2),
(p1)-[:LIKES]->(tech1),
(p2)-[:LIKES]->(tech1),
(p1)-[:LIKES]->(hobby1),
(p2)-[:LIKES]->(hobby1),
(u1)-[:PURCHASED]->(product1),
(u2)-[:PURCHASED]->(product1),
(p1)-[:ACTED_IN]->(m1),
(p2)-[:ACTED_IN]->(m2),
(m1)-[:BELONGS_TO]->(g1),
(m2)-[:BELONGS_TO]->(g1)
// 查找喜欢相同技术的人
MATCH (p1:Person)-[:LIKES]->(tech:Technology)<-[:LIKES]-(p2:Person)
WHERE p1 <> p2
RETURN p1.name AS person1, p2.name AS person2, tech.name AS common_interest
// 查找购买过相同商品的人
MATCH (u1:User)-[:PURCHASED]->(product:Product)<-[:PURCHASED]-(u2:User)
WHERE u1 <> u2
RETURN u1.name AS user1, u2.name AS user2, product.name AS common_product
// 查找有共同爱好的朋友
MATCH (p1:Person)-[:IS_FRIENDS_WITH]->(p2:Person),
(p1)-[:LIKES]->(hobby:Hobby)<-[:LIKES]-(p2)
RETURN p1.name AS person1, p2.name AS person2, hobby.name AS common_hobby
// 查找参演过相同类型电影的人
MATCH (a1:Person)-[:ACTED_IN]->(m:Movie)-[:BELONGS_TO]->(g:Genre)<-[:BELONGS_TO]-(m2:Movie)<-[:ACTED_IN]-(a2:Person)
WHERE a1 <> a2 AND m <> m2
RETURN DISTINCT a1.name AS actor1, a2.name AS actor2, g.name AS genre10. 小结 #
10.1 核心概念 #
| 概念 | 说明 | 主要用途 |
|---|---|---|
| WHERE子句 | 过滤查询结果 | 根据条件筛选数据 |
| 范围查询 | 查询数值范围 | 查找在某个区间内的数据 |
| 属性检查 | 检查属性是否存在 | 查找有/没有某个属性的节点 |
| 字符串匹配 | 字符串模式匹配 | 查找符合某种模式的字符串 |
| 模式过滤 | 使用模式作为条件 | 根据关系模式筛选数据 |
| OPTIONAL MATCH | 可选匹配 | 显示所有节点,无论是否有关系 |
10.2 查询优化建议 #
- 在MATCH中尽可能指定标签和属性:这样可以缩小搜索范围,提高查询效率
- 使用WHERE子句处理复杂条件:对于需要组合多个条件的查询,使用WHERE子句更清晰
- 合理使用LIMIT:当结果很多时,使用LIMIT可以避免返回过多数据
- 使用索引:为常用查询字段创建索引可以提高查询速度(高级主题,后续教程会介绍)
10.3 最佳实践 #
- 在MATCH中尽可能指定标签和属性
- 使用WHERE子句处理复杂条件
- 使用DISTINCT去除重复结果
- 使用LIMIT限制大型查询结果
- 使用OPTIONAL MATCH显示所有数据(即使没有关系)