PostgreSQL 中 LIKE、SIMILAR TO 或正则表达式的模式匹配

Luc*_*man 123 postgresql index regular-expression pattern-matching string-searching

我必须编写一个简单的查询,在其中查找以 B 或 D 开头的人名:

SELECT s.name 
FROM spelers s 
WHERE s.name LIKE 'B%' OR s.name LIKE 'D%'
ORDER BY 1
Run Code Online (Sandbox Code Playgroud)

我想知道是否有办法重写它以提高性能。所以我可以避免or和/或like

Erw*_*ter 196

您的查询几乎是最佳的。语法不会变得更短,查询也不会变得更快:

SELECT name
FROM   spelers
WHERE  name LIKE 'B%' OR name LIKE 'D%'
ORDER  BY 1;
Run Code Online (Sandbox Code Playgroud)

如果您真的想缩短语法,请使用带有分支的正则表达式:

...
WHERE  name ~ '^(B|D).*'
Run Code Online (Sandbox Code Playgroud)

或者稍微快一点,使用字符类

...
WHERE  name ~ '^[BD].*'
Run Code Online (Sandbox Code Playgroud)

SIMILAR TO对我来说,没有索引的快速测试比在任何一种情况下都会产生更快的结果。
有了适当的 B 树索引,就LIKE以数量级的优势赢得这场比赛。

阅读手册中有关模式匹配的基础知识。

卓越性能指标

如果您关心性能,请为更大的表创建这样的索引以支持左锚定搜索模式(从字符串的开头匹配):

CREATE INDEX spelers_name_special_idx ON spelers (name COLLATE "C");
Run Code Online (Sandbox Code Playgroud)

需要Postgres 9.1 添加的每列整理支持

看:

在使用“C”语言环境(非典型)运行的数据库中,一个普通的 B 树索引可以完成这项工作。

在旧版本中(如果您坚持,现在仍然如此),您可以将特殊运算符类text_pattern_ops用于相同的目的:

CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);
Run Code Online (Sandbox Code Playgroud)

SIMILAR TO或者带有基本左锚表达式的正则表达式也可以使用这个索引。但不适用于分支(B|D)或字符类[BD](至少在我对 PostgreSQL 9.0 的测试中)。

Trigram 匹配或文本搜索使用特殊的 GIN 或 GiST 索引。

模式匹配运算符概述

  • LIKE( ~~) 简单快速,但功能有限。
    ILIKE( ~~*) 不区分大小写的变体。
    pg_trgm 扩展了对两者的索引支持。

  • ~ (regular expression match) 功能强大但更复杂,除了基本表达式之外,可能会很慢。

  • SIMILAR TO只是毫无意义LIKE和正则表达式的奇特混血儿。我从不使用它。见下文。

  • %是“相似性”运算符,由附加模块提供pg_trgm。见下文。

  • @@是文本搜索运算符。见下文。

pg_trgm - 三字匹配

PostgreSQL 9.1开始,您可以促进扩展pg_trgm以使用 GIN 或 GiST 索引为任何 LIKE/ILIKE模式(以及带有 的简单正则表达式模式~)提供索引支持。

详细信息、示例和链接:

pg_trgm还提供了这些运算符

  • % - “相似性”运算符
  • <%(commutator: %>) - Postgres 9.6 或更高版本中的“word_similarity”运算符
  • <<%(commutator: %>>) - Postgres 11 或更高版本中的“strict_word_similarity”运算符

文字搜索

是一种特殊类型的模式匹配,具有单独的基础结构和索引类型。它使用字典和词干提取,是在文档中查找单词的好工具,尤其是对于自然语言。

还支持前缀匹配

以及自 Postgres 9.6 以来的短语搜索

考虑手册中介绍以及运算符和功能概述

用于模糊字符串匹配的附加工具

附加模块fuzzystrmatch提供了更多选项,但性能通常不如上述所有模块。

特别地,该levenshtein()功能的各种实现可能是有用的。

为什么正则表达式 ( ~) 总是比 快SIMILAR TO

答案很简单。SIMILAR TO表达式在内部被重写为正则表达式。因此,对于每个SIMILAR TO表达式,至少有一个更快的正则表达式(这样可以节省重写表达式的开销)。使用SIMILAR TO ever没有性能提升。

无论如何,可以用LIKE( ~~)完成的简单表达式会更快LIKE

SIMILAR TO仅在 PostgreSQL 中受支持,因为它最终出现在 SQL 标准的早期草案中。他们还没有摆脱它。但是有计划将其删除并包含正则表达式匹配 - 或者我听说过。

EXPLAIN ANALYZE揭示它。自己尝试任何桌子!

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO 'B%';
Run Code Online (Sandbox Code Playgroud)

显示:

...  
Seq Scan on spelers  (cost= ...  
  Filter: (name ~ '^(?:B.*)$'::text)
Run Code Online (Sandbox Code Playgroud)

SIMILAR TO已用正则表达式 ( ~)重写。

这种特殊情况的终极性能

EXPLAIN ANALYZE揭示的更多。尝试使用上述索引:

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ '^B.*;
Run Code Online (Sandbox Code Playgroud)

显示:

SELECT name
FROM   spelers
WHERE  name LIKE 'B%' OR name LIKE 'D%'
ORDER  BY 1;
Run Code Online (Sandbox Code Playgroud)

在内部,对于不text_pattern_ops识别语言环境(或使用语言环境C)的索引,使用以下文本模式运算符重写简单的左锚表达式:~>=~, ~<=~, ~>~, ~<~。这是的情况下~~~SIMILAR TO相似。

对于varchar带有varchar_pattern_opschar带有 的类型的索引也是如此bpchar_pattern_ops

因此,应用于原始问题,这是最快的方法

SELECT name
FROM   spelers  
WHERE  name ~>=~ 'B' AND name ~<~ 'C'
    OR name ~>=~ 'D' AND name ~<~ 'E'
ORDER  BY 1;
Run Code Online (Sandbox Code Playgroud)

当然,如果你碰巧搜索相邻的首字母,你可以进一步简化:

WHERE  name ~>=~ 'B' AND name ~<~ 'D'   -- strings starting with B or C
Run Code Online (Sandbox Code Playgroud)

简单使用~or的收益~~很小。如果性能不是您的首要要求,您应该坚持使用标准运算符 - 达到您在问题中已有的内容。

  • @MartinSmith:使用“EXPLAIN ANALYZE”进行的快速测试显示了 2 个位图索引扫描。可以相当快速地组合多个位图索引扫描。 (2认同)
  • @a_horse_with_no_name:我不希望。带有 GIN 索引的 pg_tgrm 的新功能是对通用文本搜索的一种享受。在开始时锚定的搜索已经比这更快了。 (2认同)

one*_*hen 11

如何向表中添加一列。根据您的实际需求:

person_name_start_with_B_or_D (Boolean)

person_name_start_with_char CHAR(1)

person_name_start_with VARCHAR(30)
Run Code Online (Sandbox Code Playgroud)

PostgreSQL 不支持基表中的计算列,但可以通过触发器维护新列。显然,这个新列将被索引。

或者,表达式上索引会给你同样的、更便宜的。例如:

CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1)); 
Run Code Online (Sandbox Code Playgroud)

与其条件中的表达式匹配的查询可以使用此索引。

这样,在创建或修改数据时会影响性能,因此可能仅适用于低活动环境(即写入比读取少得多)。


Mar*_*ith 8

你可以试试

SELECT s.name
FROM   spelers s
WHERE  s.name SIMILAR TO '(B|D)%' 
ORDER  BY s.name
Run Code Online (Sandbox Code Playgroud)

不过,我不知道上述内容或您的原始表达式是否可以在 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
Run Code Online (Sandbox Code Playgroud)