第4章:过滤与条件——FILTER关键字、比较运算符、逻辑运算符、字符串匹配与正则
查询数据的时候,你不可能每次都把整个库搬出来。在SPARQL里,FILTER 就是那个帮你「挑着拿」的家伙。我个人觉得,这玩意儿的地位比大多数初学者想象得高得多——它决定了你的查询是精准手术刀,还是大铁锹。
先看个最基础的样子:
SELECT ?person ?age
WHERE {
?person :hasAge ?age .
FILTER(?age > 18)
}
很简单对吧?FILTER 里面放一个布尔表达式,为真的行留下,为假的扔掉。不过现实中的条件可没这么单纯,咱们一步步来。
4.1 比较运算符
SPARQL 支持的比较运算符跟大多数语言差不多。我直接列个对照表,你一眼就能懂:
| 运算符 | 含义 | 示例 |
|---|---|---|
= |
等于 | FILTER(?x = 42) |
!= |
不等于 | FILTER(?x != 42) |
< |
小于 | FILTER(?x < 42) |
> |
大于 | FILTER(?x > 42) |
<= |
小于等于 | FILTER(?x <= 42) |
>= |
大于等于 | FILTER(?x >= 42) |
这些跟SQL基本一致,但有一个坑:不能直接拿 = 去比较URI或字面值的类型不匹配。举个例子:
# 这样写,很可能没结果
FILTER(?age = "18")
# 你应该写
FILTER(?age = 18)
前者是字符串 "18",后者是数值 18。类型不同,= 返回 false。我在项目里见过有人排错排了一下午,最后发现是引号惹的祸。
4.2 逻辑运算符
一个条件往往不够。SPARQL 给了你三个逻辑组合工具:&&(与)、||(或)、!(非)。
写个实际点的例子:
SELECT ?person ?age ?city
WHERE {
?person :hasAge ?age .
?person :livesIn ?city .
FILTER(?age > 18 && ?city = :Beijing)
}
这段的意思是:找出年龄大于18岁、并且住在北京的人。注意 && 两边的括号可以省略,但我个人习惯加一组,防止优先级闹鬼。
逻辑运算符的优先级是这样的:
!优先最高&&次之||最低
你想表达「不是老人也不是小孩」:
FILTER(!(?age < 18 || ?age > 60))
避坑指南:我曾经在一条FILTER里用了三个 || 和两个 &&,没加括号。结果跑出来数据明显不对,调试了半小时才发现是优先级问题。从此以后,但凡超过两个条件我就加括号,不给自己挖坑。
4.3 字符串匹配
试想你要查所有名字里带「张」的人。用 = 肯定不行,因为要的是包含,不是全等。这时候得用 CONTAINS 或 STRSTARTS 这类字符串函数。
常见字符串匹配函数:
| 函数 | 作用 | 示例 |
|---|---|---|
CONTAINS(?str, "keyword") |
包含子串 | 查含「张」的名字 |
STRSTARTS(?str, "prefix") |
以某前缀开头 | 查以「张」开头的名字 |
STRENDS(?str, "suffix") |
以某后缀结尾 | 查以「.com」结尾的邮箱 |
STR(?term) |
提取字面值字符串 | 先把URI转成纯字符串再匹配 |
看个完整的:
SELECT ?person ?name
WHERE {
?person :name ?name .
FILTER(CONTAINS(STR(?name), "张"))
}
这里有个微妙点——?name可能是带语言标签的字面值(比如 "张三"@zh),直接用 CONTAINS 有时候会类型报错。所以我习惯先 STR(?name) 转成纯字符串,再匹配。
4.4 正则表达式
字符串匹配如果只靠 CONTAINS,很多场景搞不定。比如你想匹配手机号格式、邮箱规则、或者某种模式。这时候就得请出正则大神——REGEX。
基本语法:
FILTER(REGEX(?str, "pattern", "flags"))
?str:待匹配的字符串pattern:正则模式串flags:可选,常用i表示忽略大小写
来个实战:
# 查所有以 1 开头、11位数字的手机号
SELECT ?person ?phone
WHERE {
?person :phone ?phone .
FILTER(REGEX(STR(?phone), "^1\\d{10}$"))
}
注意:反斜杠 \ 在SPARQL字符串里得写成 \\。比如 \d 要写成 \\d。这个细节容易忘,我刚开始写正则就经常踩这个坑。
再看一个忽略大小写的例子——查所有Gmail邮箱,大小写通吃:
FILTER(REGEX(STR(?email), "@gmail\\.com$", "i"))
正则虽然强大,但也要注意性能。我在项目中优化过一条查询,原来的 REGEX 写在很深的嵌套里,数据量上了百万以后直接超时。后来我们把正则条件前置,配合索引,速度提升了十几倍。
4.5 FILTER 的性能陷阱
嗯,这里要重点说一下。很多新手喜欢把所有过滤都塞进 FILTER,觉得方便。但你要明白:FILTER 是在数据被初步检索后,再进行逐行过滤的。
举个例子:
# 低效写法
SELECT ?person ?age
WHERE {
?person :hasAge ?age .
?person :type :Student .
FILTER(?age > 18)
}
# 高效写法(如果可以)
SELECT ?person ?age
WHERE {
?person :hasAge ?age .
?person :type :Student .
?person :hasAge ?age2 .
FILTER(?age2 > 18)
}
等等,这两个看起来差不多?不,关键在于:如果三元组模式本身就能筛选数据,尽量先用三元组模式,FILTER 是最后一道闸。能用资源约束(比如 :type :Student)过滤掉大部分数据的,就别等到 FILTER 里才做。
另外,REGEX 尤其在文本字段上用起来费劲。能不用正则就尽量别用,优先用 STRSTARTS 或 CONTAINS。这些函数在某些引擎上是有索引支持的,正则通常没有。
来看看本章的知识体系结构:
总结一下,FILTER 的使用策略其实就三步:判断条件类型 → 选择合适的运算符或函数 → 注意性能。说白了,这层过滤的功力深浅,直接决定了你的查询在数据量大了以后能不能跑得动。
核心要点回顾:
- 比较运算符注意类型一致
- 逻辑运算符善用括号理顺优先级
- 字符串匹配优先
STRSTARTS/CONTAINS,性能优于 REGEX - REGEX 中反斜杠要写双份,加上
"i"标志忽略大小写 - FILTER 是最后一道闸,能用三元组模式过滤的别扔给它