模式匹配,类似于PostgreSQL中的类似或正则表达式
-
16-10-2019 - |
题
我不得不写一个简单的查询,我去寻找以B或A开头的人们的名字:
SELECT s.name
FROM spelers s
WHERE s.name LIKE 'B%' OR s.name LIKE 'D%'
ORDER BY 1
我想知道是否有一种方法可以改写这一表现。所以我可以避免 or
和 /或 like
?
解决方案
您的查询几乎是最佳的。语法不会缩短太多,查询不会变得更快:
SELECT name
FROM spelers
WHERE name LIKE 'B%' OR name LIKE 'D%'
ORDER BY 1;
如果你真的想 缩短语法, ,使用正则表达式 分支:
...
WHERE name ~ '^(B|D).*'
或稍微快一点 角色类:
...
WHERE name ~ '^[BD].*'
没有索引的快速测试会产生更快的结果 SIMILAR TO
无论哪种情况,我。
有了适当的b-tree索引, LIKE
通过数量级赢得这场比赛。
阅读有关的基础知识 手册中的图案匹配.
卓越性能索引
如果您关心性能,请为更大表格创建这样的索引:
CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);
通过数量级使这种查询更快。特殊考虑的特定于区域的排序顺序。阅读更多有关 手册中的操作员课程. 。如果您使用的是标准“ C”语言环境(大多数人不使用),则可以使用普通索引(带有默认操作员类)。
这样的索引仅适用于左锚定模式(从字符串的开头匹配)。
SIMILAR TO
或带有基本左锚定表达式的正则表达式也可以使用此索引。但 不是 有分支 (B|D)
或角色类 [BD]
(至少在我对PostgreSQL 9.0的测试中)。
Trigram匹配或文本搜索使用特殊的杜松子酒或要点索引。
模式匹配操作员的概述
~
(正则表达式匹配)功能强大,但更复杂,除了基本表达式外,任何事情都可能更慢。SIMILAR TO
只是 无意义. 。一个奇特的半身LIKE
和正则表达式。我从不使用它。见下文。% 是“相似性”运算符,由附加模块提供
pg_trgm
. 。见下文。@@
是文本搜索操作员。见下文。
pg_trgm-搭配
以。。。开始 Postgresql 9.1 您可以促进扩展 pg_trgm
为指数支持 任何 LIKE
/ ILIKE
模式(以及带有简单的REGEXP模式 ~
)使用杜松子酒或要点索引。
详细信息,示例和链接:
pg_trgm
也提供 这些操作员:
%
- “相似性”运营商<%
(换向器:%>
) - Postgres 9.6或更高版本中的“ Word_simurility”运算符<<%
(换向器:%>>
) - Postgres 11或以后
文本搜索
是一种与单独的基础架构和索引类型的特殊类型匹配。它使用词典和词干,是在文档中查找单词的绝佳工具,尤其是对于自然语言。
前缀匹配 也得到支持:
也 短语搜索 由于Postgres 9.6:
模糊字符串匹配的其他工具
附加模块 fuzzyStrMatch 提供更多选择,但性能通常不如上述所有。
特别是,各种实现 levenshtein()
功能可能是工具性的。
为什么是正则表达式(~
)总是比 SIMILAR TO
?
答案很简单。 SIMILAR TO
表达式在内部重写正则表达式。所以,每个 SIMILAR TO
表达,有 至少 一个更快的正则表达式(这节省了重写表达式的开销)。使用没有性能 SIMILAR TO
曾经.
以及可以完成的简单表达 LIKE
(~~
)更快 LIKE
反正。
SIMILAR TO
仅在PostgreSQL中得到支持,因为它最终是SQL标准的早期草稿。他们仍然没有摆脱它。但是有计划将其删除并包括RegeXP匹配 - 或者我听说。
EXPLAIN ANALYZE
揭示它。自己尝试任何桌子!
EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO 'B%';
揭示:
...
Seq Scan on spelers (cost= ...
Filter: (name ~ '^(?:B.*)$'::text)
SIMILAR TO
已经用正则表达式重写(~
).
这种特殊情况的最终表现
但 EXPLAIN ANALYZE
揭示更多。尝试使用上述索引:
EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ '^B.*;
揭示:
...
-> Bitmap Heap Scan on spelers (cost= ...
Filter: (name ~ '^B.*'::text)
-> Bitmap Index Scan on spelers_name_text_pattern_ops_idx (cost= ...
Index Cond: ((prod ~>=~ 'B'::text) AND (prod ~<~ 'C'::text))
在内部,有一个不感知的索引(text_pattern_ops
或使用语言环境 C
)这些文本模式运算符重写简单的左派表达式: ~>=~
, ~<=~
, ~>~
, ~<~
. 。就是这样 ~
, ~~
或者 SIMILAR TO
一样。
索引也是如此 varchar
类型 varchar_pattern_ops
或者 char
和 bpchar_pattern_ops
.
因此,应用于原始问题,这是 最快的方法:
SELECT name
FROM spelers
WHERE name ~>=~ 'B' AND name ~<~ 'C'
OR name ~>=~ 'D' AND name ~<~ 'E'
ORDER BY 1;
当然,如果您碰巧搜索 相邻的缩写, ,您可以进一步简化:
WHERE name ~>=~ 'B' AND name ~<~ 'D' -- strings starting with B or C
明确使用的收益 ~
或者 ~~
很小。如果性能不是您的最高要求,则应该坚持使用标准运算符 - 到达问题中已经拥有的东西。
其他提示
如何在表中添加一列。取决于您的实际要求:
person_name_start_with_B_or_D (Boolean)
person_name_start_with_char CHAR(1)
person_name_start_with VARCHAR(30)
PostgreSQL不支持 基础表中的计算列LA SQL Server 但是新列可以通过触发来维护。显然,此新列将被索引。
或者,一个 表达式索引 会给你一样便宜。例如:
CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1));
在其条件下与表达式匹配的查询可以利用此索引。
这样,在创建或修改数据时会进行性能命中,因此可能仅适用于低活动环境(即写入比阅读少得多)。
你可以 尝试
SELECT s.name
FROM spelers s
WHERE s.name SIMILAR TO '(B|D)%'
ORDER BY s.name
我不知道以上或您的原始表达方式在Postgres中是否符合。
如果您创建建议的索引也有兴趣听到与其他选项进行比较的方式。
SELECT name
FROM spelers
WHERE name >= 'B' AND name < 'C'
UNION ALL
SELECT name
FROM spelers
WHERE name >= 'D' AND name < 'E'
ORDER BY name
我过去所做的面临类似的性能问题是要递增最后一个字母的ASCII特征,并在介于两者之间。然后,对于类似功能的子集,您将获得最佳性能。当然,它仅在某些情况下起作用,但是对于您要在名称上搜索的超大数据集,它使性能从Abysmal到可接受。
非常古老的问题,但我找到了一个解决这个问题的另一个快速解决方案:
SELECT s.name
FROM spelers s
WHERE ascii(s.name) in (ascii('B'),ascii('D'))
ORDER BY 1
由于函数ascii()仅查看字符串的第一个字符。
要检查缩写,我经常使用铸造 "char"
(带双引号)。它不是便携式的,但是很快。在内部,它只是detostost and detotost and返回第一个字符,而“ char”比较操作非常快,因为该类型为1字节固定长度:
SELECT s.name
FROM spelers s
WHERE s.name::"char" =ANY( ARRAY[ "char" 'B', 'D' ] )
ORDER BY 1
请注意铸造 "char"
比 ascii()
@sole021的slute,但它不兼容UTF8(或任何其他编码),仅返回第一个字节,因此仅在比较与普通的旧7位ASCII字符的情况下使用。
尚未提及处理此类情况的两种方法:
部分(或分区 - 如果手动为全范围创建)索引 - 当仅需要数据子集时(例如,在某些维护期间或临时报告中)最有用:
CREATE INDEX ON spelers WHERE name LIKE 'B%'
将表本身划分(使用第一个字符作为分区键) - 在PostgreSQL 10+(较少痛苦的分区)和11+(查询执行过程中的分区修剪)中,该技术尤其值得考虑。
此外,如果对表中的数据进行排序,则可以从使用中受益 Brin索引 (在第一个字符上)。
进行单个角色比较的速度可能更快:
SUBSTR(s.name,1,1)='B' OR SUBSTR(s.name,1,1)='D'