Postgres中LIKE和〜之间的区别


74

我被指示“不要打扰LIKE”,而要使用~。什么是错的LIKE又是怎样的~不同?

~在这种情况下是否有名称,或者人们说“使用波浪号运算符”?


由谁指示,出于兴趣?我很好奇。
Craig Ringer 2012年

@CraigRinger课程讲师。
Celeritas

知道了,谢谢。对不起,我的意思不是“谁”,而是“哪里”。
克雷格·林格

Answers:


58

~是正则表达式运算符,并具有其隐含的功能。您可以指定所有正则表达式通配符和量词;有关详细信息,请参见文档。它肯定比强大LIKE,并且在需要此功能时应使用,但它们具有不同的用途。


36
这是非常糟糕的建议。每当您可以使用SQL标准时LIKE,通常最好使用正则表达式。通过适当的索引,它更简单,更快,更容易使用。正则表达式功能更强大,但速度较慢且非标准。
Erwin Brandstetter,2012年

12
这就是为什么我说“在适当的时候,但是它们有不同的目的”。
asthasr 2012年

14
should be used in preference to the LIKE operator when appropriate是错的。当您有选择时,请使用LIKE。如果您别无选择,那么您也别无选择。
Erwin Brandstetter 2012年

4
您是否没有看到有人试图将鞋拔模式匹配到具有多个LIKE子句的查询中?在其他一些系统中,这是必需的,但是Postgres提供了regex运算符。也许我应该说:“应该优先使用多个LIKE子句。”
asthasr 2012年

4
相应地删除了downvote,因为答案不再让我觉得错了。
Erwin Brandstetter,2012年

38

LIKEIMO没有错,也没有理由~对此表示赞同。相反。LIKE是SQL标准的。也是SIMILAR TO,但并未得到广泛支持。PostgreSQL的~ operator(或posix正则表达式匹配运算符)不是SQL标准。

因此,我更喜欢LIKE在表达力足够大的地方使用,并且仅~在需要完整正则表达式的功能时才使用。如果我需要移植数据库,那将是一件痛苦的事。我倾向于使用SIMILAR TOwhenLIKE不够强大的功能,但是在Erwin发表评论之后,我想我会停止这样做,而~LIKE不工作时使用。

此外,PostgreSQL的可以使用前缀搜索(例如B-tree索引LIKE 'TEST%')用LIKESIMILAR TO如果数据库是在C区域或索引有text_pattern_ops。与我之前写的相反,Pg也可以为左锚posix正则表达式使用这样的索引,它只需要一个显式的'^ TEST。*',因此正则表达式只能从头开始匹配。我之前的帖子错误地指出~不能使用索引进行前缀搜索。消除了这种差异,实际上取决于您是否愿意坚持使用标准兼容功能。

看到这个演示SQLFiddle ; 注意不同的执行计划。注意之间的差异~ '1234.*'~ '^1234.*'

给定样本数据:

create table test (
   blah text
);
insert into test (blah)  select x::text from generate_series(1,10000) x;
create index test_blah_txtpat_idx ON test(blah text_pattern_ops);

请注意,~即使在使用seqscan时,它也要使用索引,即使它实际上更昂贵(由于enable_seqscan之所以如此),因为它别无选择LIKE。但是,~左锚定的校正也使用索引:

regress=# SET enable_seqscan = 'f';
SET
regress=# explain select 1 from test where blah ~ '12.*';
                                QUERY PLAN                                 
---------------------------------------------------------------------------
 Seq Scan on test  (cost=10000000000.00..10000000118.69 rows=2122 width=0)
   Filter: (blah ~ '12.*'::text)
(2 rows)
regress=# explain select 1 from test where blah like '12%';
                                     QUERY PLAN                                     
------------------------------------------------------------------------------------
 Bitmap Heap Scan on test  (cost=4.55..46.76 rows=29 width=0)
   Filter: (blah ~~ '12%'::text)
   ->  Bitmap Index Scan on test_blah_txtpat_idx  (cost=0.00..4.54 rows=29 width=0)
         Index Cond: ((blah ~>=~ '12'::text) AND (blah ~<~ '13'::text))
(4 rows)
regress=# explain select 1 from test where blah ~ '^12.*';
                                     QUERY PLAN                                      
-------------------------------------------------------------------------------------
 Bitmap Heap Scan on test  (cost=5.28..51.53 rows=101 width=0)
   Filter: (blah ~ '^12.*'::text)
   ->  Bitmap Index Scan on test_blah_txtpat_idx  (cost=0.00..5.25 rows=100 width=0)
         Index Cond: ((blah ~>=~ '12'::text) AND (blah ~<~ '13'::text))
(4 rows)

我个人不同意SIMILAR TO。我在回答中写了更多有关此的内容。另外,值得一提的是,即使对于正则表达式运算符,也支持非常常见的左锚搜索模式~
Erwin Brandstetter 2012年

@ErwinBrandstetter啊,我忘了锚定它了。将纠正我的帖子。
Craig Ringer 2012年

@ErwinBrandstetter已更正。谢谢。
Craig Ringer 2012年

32

总览

LIKESIMILAR TO并且~是基本的PostgreSQL中模式匹配运营商

如果可以,请使用LIKE~~),这是最快,最简单的。
如果不能,请使用正则表达式(~),功能更强大。
永远不要用户SIMILAR TO。没有用。见下文。

安装附加模块pg_trgm将添加高级索引选项和相似性运算符%文本搜索
还具有其自身的基础设施和运营商(以及其他)。@@

这些操作员中的每一个都可以使用索引支持-程度不同。它经常胜过其他选择的性能。但是即使在使用索引的情况下,细节也有很多余地。

索引支持

没有pg_trgm,只有索引支持左锚定搜索模式。如果您的数据库集群使用非C语言环境运行(典型情况),则需要一个带有特殊运算符类的索引,例如text_pattern_opsvarchar_pattern_ops。基本的左固定正则表达式也受此支持。例:

CREATE TABLE tbl(string text);

INSERT INTO  tbl(string)
SELECT x::text FROM generate_series(1, 10000) x;

CREATE INDEX tbl_string_text_pattern_idx ON tbl(string text_pattern_ops);

SELECT * FROM tbl WHERE string ~ '^1234';  -- left anchored pattern

SQL提琴。

随着pg_trgm安装,杜松子酒或GiST索引可能与运营商类gist_trgm_opsgin_trgm_ops。这些索引支持任何 LIKE表达式,而不仅仅是锚定。并且,引用手册:

从PostgreSQL 9.3开始,这些索引类型还支持对正则表达式匹配项进行索引搜索。

细节:


SIMILAR TO是一个非常奇怪的结构。PostgreSQL只实现它,因为它是在SQL标准的早期版本中定义的。在内部,每个SIMILAR TO表达式都用正则表达式重写。因此,对于任何给定的SIMILAR TO表达式,至少有一个正则表达式可以更快地完成相同的工作。我从不使用SIMILAR TO

进一步阅读:


3
我最近读到,SIMILAR TO无论如何,它可能会从ANSI SQL标准的下一版本中消除(支持REGEXP_LIKE)。而且我不确定使用SIMILAR TO过度带来的可移植性收益~是否值得,因为其他DBMS(Oracle,SQL Server,MySQL)不支持该标准。
David Faber 2012年

@DavidFaber:我完全同意。
Erwin Brandstetter,2012年

7

3
只是一种意见(因此是一种评论),但我不喜欢该~~运算符,因为它是Postgres特有的,并且失去了SQL可移植性而没有真正的收获。
asthasr 2012年

3
@syrion:很抱歉,我不清楚,我完全同意,我只是说那~并没有做同样的事情LIKE

@syrion搜索起来并不容易LIKE,例如,我不得不tilde tilde在Google上键入才能到达这里
Dorian

6

我只是做了一个快速而简单的基准测试,以研究不涉及索引的情况下两个运算符之间的性能差异:

postgres=# \timing
Timing is on.
postgres=# SELECT count(1) FROM (SELECT val from generate_series(1, 10000000) x(val) WHERE val::text LIKE '%5%') AS x;
  count
─────────
 5217031
(1 row)

Time: 5631.662 ms
postgres=# SELECT count(1) FROM (SELECT val from generate_series(1, 10000000) x(val) WHERE val::text ~ '5') AS x;
  count
─────────
 5217031
(1 row)

Time: 10612.406 ms

在此示例中,LIKE操作员的速度几乎是操作员的两倍~。因此,如果速度至关重要,那么我会倾向于LIKE,尽管要注意不要过早优化。~给您更大的灵活性。

对于有兴趣的人,EXPLAIN以下是上述查询的计划:

postgres=# EXPLAIN ANALYZE SELECT count(1) FROM (SELECT val from generate_series(1, 10000000) x(val) WHERE val::text LIKE '%5%') AS x;
                                                              QUERY PLAN
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Aggregate  (cost=20.00..20.01 rows=1 width=0) (actual time=9967.748..9967.749 rows=1 loops=1)
   ->  Function Scan on generate_series x  (cost=0.00..17.50 rows=1000 width=0) (actual time=1732.084..7404.755 rows=5217031 loops=1)
         Filter: ((val)::text ~~ '%5%'::text)
         Rows Removed by Filter: 4782969
 Total runtime: 9997.587 ms
(5 rows)

postgres=# EXPLAIN ANALYZE SELECT count(1) FROM (SELECT val from generate_series(1, 10000000) x(val) WHERE val::text ~ '5') AS x;
                                                              QUERY PLAN
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Aggregate  (cost=20.00..20.01 rows=1 width=0) (actual time=15118.061..15118.061 rows=1 loops=1)
   ->  Function Scan on generate_series x  (cost=0.00..17.50 rows=1000 width=0) (actual time=1724.591..12516.996 rows=5217031 loops=1)
         Filter: ((val)::text ~ '5'::text)
         Rows Removed by Filter: 4782969
 Total runtime: 15147.950 ms
(5 rows)

2
哇,那是一个巨大的差异。很好的例子!
乔丹·帕默

2
您能否提供解释计划,以查看对增量的解释是否与上面Craig Ringer的解释一致?
eharik'3

1
很好的建议,@ eharik。做完了
亚历克斯(Alex)

1
因此,使用无索引的测试数据集(如您在此处显示的内容),您实际上只是在确认Erwin在说什么。即~~只是比〜更快的运算符。
eharik'3

5

就像只匹配字符串的一部分,无论是在开头还是在末尾或中间。倾斜(〜)与正则表达式匹配

为了进一步解释这一点,让我们创建一个表并插入一些值

# create table users(id serial primary key, name character varying);

现在让我们在表格中插入一些值

# insert into users (name) VALUES ('Alex'), ('Jon Snow'), ('Christopher'), ('Arya'),('Sandip Debnath'), ('Lakshmi'),('alex@gmail.com'),('@sandip5004'), ('lakshmi@gmail.com');

现在你的桌子应该看起来像这样

 id |       name        
----+-------------------
  1 | Alex
  2 | Jon Snow
  3 | Christopher
  4 | Arya
  5 | Sandip Debnath
  6 | Lakshmi
  7 | alex@gmail.com
  8 | lakshmi@gmail.com
  9 | @sandip5004

案例喜欢

# select * from users where name like 'A%';
 id | name 
----+------
  1 | Alex
  4 | Arya
(2 rows)

如您所见, 'A%'只会得到名称以大写A开头的值。

# select * from users where name like '%a%';
 id |       name        
----+-------------------
  4 | Arya
  5 | Sandip Debnath
  6 | Lakshmi
  7 | alex@gmail.com
  8 | lakshmi@gmail.com

如您所见, '%a%'只会为我们获取名称a在名称之间的值。

# select * from users where name like '%a';

 id | name 
----+------
  4 | Arya

如您所见, '%a'只会得到名称以结尾的值a

案例〜(倾斜)

# select * from users where name ~* 't';
 id |      name      
----+----------------
  3 | Christopher
  5 | Sandip Debnath

如您所见, name ~* 't'只会获得名称为的值t~表示区分大小写,〜*表示不区分大小写,因此

# select * from users where name ~ 'T';
 id | name 
----+------
(0 rows)

上面的查询给了我们0行,因为T它与任何条目都不匹配

现在让我们考虑一种情况,我们只需要获取电子邮件ID,我们不知道邮件ID有什么,但是我们知道电子邮件的模式,即会有一些字母或数字或_或。或-然后是@,然后再输入一些字母或数字,或-然后。然后使用comorinorg etcand我们可以使用正则表达式创建模式。

现在让我们尝试使用正则表达式获取结果

# select * from users where name ~* '[a-z0-9\.\-\_]+@[a-z0-9\-]+\.[a-z]{2,5}';
 id |       name        
----+-------------------
  7 | alex@gmail.com
  8 | lakshmi@gmail.com

同样,我们可以获取一些名称,它们之间有一个空格

#select * from users where name ~* '[a-z]+\s[a-z]+';
 id |      name      
----+----------------
  2 | Jon Snow
  5 | Sandip Debnath

[az] +表示从a到z的任何字母,+表示可能出现1次或多次,\ s表示此后之间将有一个空格,然后又出现一组字母,可能出现1次或多次次。

希望这个详细的分析有所帮助。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.