第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岁、并且住在北京的人。注意 && 两边的括号可以省略,但我个人习惯加一组,防止优先级闹鬼。

逻辑运算符的优先级是这样的:

  1. ! 优先最高
  2. && 次之
  3. || 最低

你想表达「不是老人也不是小孩」:

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 过滤与条件决策流程 原始三元组匹配结果 构建过滤条件表达式 条件类型 判断 比较 / 逻辑 字符串匹配 正则 REGEX 最终过滤结果输出 => < > <= >= && || ! CONTAINS / STRSTARTS STRENDS / STR REGEX(?str, "pattern", "flags")

总结一下,FILTER 的使用策略其实就三步:判断条件类型 → 选择合适的运算符或函数 → 注意性能。说白了,这层过滤的功力深浅,直接决定了你的查询在数据量大了以后能不能跑得动。

核心要点回顾:

  • 比较运算符注意类型一致
  • 逻辑运算符善用括号理顺优先级
  • 字符串匹配优先 STRSTARTS / CONTAINS,性能优于 REGEX
  • REGEX 中反斜杠要写双份,加上 "i" 标志忽略大小写
  • FILTER 是最后一道闸,能用三元组模式过滤的别扔给它