J'ai été invité à "ne pas déranger avec LIKE
" et à utiliser ~
au lieu. Quel est le problème avec LIKE
et comment ~
différent?
Est-ce que ~
ont un nom dans ce contexte ou les gens disent "utiliser l'opérateur tilde"?
~
est l'opérateur d'expression régulière et possède les capacités que cela implique. Vous pouvez spécifier une gamme complète de caractères génériques et de quantificateurs d'expression régulière; voir la documentation pour plus de détails. Il est certainement plus puissant que LIKE
, et devrait être utilisé lorsque cette puissance est nécessaire, mais il sert à des fins différentes.
Il n'y a rien de mal avec LIKE
et, à mon avis, aucune raison de favoriser ~
par-dessus. Plutôt le contraire. LIKE
est standard SQL. Il en est de même SIMILAR TO
, mais il n'est pas largement pris en charge. PostgreSQL's ~ operator
(ou opérateur de correspondance d'expression régulière posix ) n'est pas la norme SQL.
Pour cette raison, je préfère utiliser LIKE
où il est suffisamment expressif et j'utilise uniquement ~
quand j'ai besoin de la puissance des expressions régulières complètes. Si j'ai besoin de porter des bases de données, c'est une chose de moins qui me fera mal. J'ai eu tendance à utiliser SIMILAR TO
lorsque LIKE
n'est pas assez puissant, mais après les commentaires d'Erwin, je pense que je vais arrêter de le faire et utiliser ~
lorsque LIKE
ne fait pas le travail.
De plus, PostgreSQL peut utiliser un index b-tree pour les recherches de préfixes (par exemple LIKE 'TEST%'
) avec LIKE
ou SIMILAR TO
si la base de données se trouve dans les paramètres régionaux C
ou si l'index contient text_pattern_ops
. Contrairement à ce que j'ai écrit plus tôt, Pg peut également utiliser un tel index pour une expression rationnelle posix ancrée à gauche, il a juste besoin d'un '^ TEST. *' Explicite afin que l'expression régulière ne puisse correspondre que depuis le début. Mon message plus tôt indiquait à tort que ~
n'a pas pu utiliser d'index pour une recherche de préfixe. Une fois cette différence éliminée, il s'agit vraiment de savoir si vous souhaitez vous en tenir aux fonctionnalités conformes aux normes lorsque cela est possible ou non.
Voir cette démo SQLFiddle ; noter les différents plans d'exécution. Notez la différence entre ~ '1234.*'
et ~ '^1234.*'
.
Étant donné des exemples de données:
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);
notez que ~
utilise un seqscan même lorsqu'il est beaucoup plus cher (artificiellement, en raison de enable_seqscan
) car il n'a pas d'alternative, tandis que LIKE
utilise l'index. Cependant, un ~
avec une ancre gauche utilise également l'index:
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)
LIKE
, SIMILAR TO
et ~
sont les bases opérateurs de mise en correspondance de modèles dans PostgreSQL .
Si vous le pouvez, utilisez LIKE
(~~
), c'est le plus rapide et le plus simple.
Si vous ne le pouvez pas, utilisez une expression régulière (~
), c'est plus puissant.
Jamais utilisateur . C'est inutile. Voir ci-dessous.SIMILAR TO
L'installation de module supplémentaire pg_trgm ajoute des options d'index avancées et opérateur de similarité %
.
Et il y a aussi recherche de texte avec sa propre infrastructure et @@
opérateur (entre autres).
La prise en charge de l'index est disponible pour chacun de ces opérateurs - à des degrés divers. Il l'emporte régulièrement sur les performances des autres options. Mais il y a beaucoup de latitude dans les détails, même avec les index.
Sans pg_trgm , il n'y a que le support d'index pour à gauche ancré modèles de recherche. Si votre cluster de base de données s'exécute avec un environnement local non-C (cas typique), vous avez besoin d'un index avec une classe d'opérateur spéciale pour cela, comme text_pattern_ops
ou varchar_pattern_ops
. Les expressions régulières de base ancrées à gauche sont également prises en charge par cela. Exemple:
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
Avec pg_trgm installé, les index GIN ou Gist sont possibles avec les classes d'opérateur Gist_trgm_ops
ou gin_trgm_ops
. Ces index prennent en charge l'expression anyLIKE
, pas seulement laissés ancrés. Et, en citant le manuel:
À partir de PostgreSQL 9.3, ces types d'index prennent également en charge les recherches d'index pour les correspondances d'expressions régulières.
Détails:
SIMILAR TO
est une construction très étrange. PostgreSQL ne l'implémente que parce qu'il a été défini dans les premières versions du standard SQL. En interne, chaque SIMILAR TO
l'expression est réécrite avec une expression régulière. Par conséquent, pour tout SIMILAR TO
expression, il existe au moins une expression régulière faisant le même travail plus vite. I jamais utilise SIMILAR TO
.
Lectures complémentaires:
Je viens de faire un benchmark simple et rapide pour regarder la différence de performance entre les deux opérateurs quand aucun index n'est impliqué:
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
Dans cet exemple, l'opérateur LIKE
est presque deux fois plus rapide que ~
opérateur. Donc, si la vitesse est essentielle, je pencherais vers LIKE
, mais attention à ne pas optimiser prématurément. ~
vous donne beaucoup plus de flexibilité.
Pour ceux d'entre vous qui sont intéressés, voici EXPLAIN
plans pour les requêtes ci-dessus:
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)
Comme correspond à une partie de la chaîne au début ou à la fin ou au milieu Et l'inclinaison (~) correspond à l'expression régulière
Pour expliquer cela, créons un tableau et insérons quelques valeurs
# create table users(id serial primary key, name character varying);
Insérons maintenant quelques valeurs dans le tableau
# insert into users (name) VALUES ('Alex'), ('Jon Snow'), ('Christopher'), ('Arya'),('Sandip Debnath'), ('Lakshmi'),('[email protected]'),('@sandip5004'), ('[email protected]');
Maintenant, votre table devrait ressembler à ceci
id | name
----+-------------------
1 | Alex
2 | Jon Snow
3 | Christopher
4 | Arya
5 | Sandip Debnath
6 | Lakshmi
7 | [email protected]
8 | [email protected]
9 | @sandip5004
# select * from users where name like 'A%';
id | name
----+------
1 | Alex
4 | Arya
(2 rows)
Comme vous pouvez le voir 'A%'
ne nous obtiendra que les valeurs dont le nom commence par un majuscule A.
# select * from users where name like '%a%';
id | name
----+-------------------
4 | Arya
5 | Sandip Debnath
6 | Lakshmi
7 | [email protected]
8 | [email protected]
Comme vous pouvez le voir '%a%'
ne nous obtiendra que les valeurs dont le nom a a
entre le nom.
# select * from users where name like '%a';
id | name
----+------
4 | Arya
Comme vous pouvez le voir '%a'
ne nous donnera que les valeurs dont le nom se termine par a
.
# select * from users where name ~* 't';
id | name
----+----------------
3 | Christopher
5 | Sandip Debnath
Comme vous pouvez le voir name ~* 't'
ne nous donnera que les valeurs dont le nom a t
. ~
signifie sensible à la casse et ~ * signifie insensible à la casse donc
# select * from users where name ~ 'T';
id | name
----+------
(0 rows)
la requête ci-dessus nous a donné 0 lignes car T
ne correspondait à aucune entrée
Considérons maintenant un cas où nous n'avons besoin que de récupérer les identifiants des e-mails et nous ne savons pas quels sont les identifiants des e-mails, mais nous connaissons le modèle d'e-mail, c'est-à-dire qu'il y aura une lettre ou un chiffre ou _ ou. ou - puis @ et puis encore une lettre ou un chiffre ou - alors. alors com
ou in
ou org
etc
et nous pouvons créer le modèle en utilisant l'expression régulière.
essayons maintenant de récupérer les résultats en utilisant l'expression régulière
# select * from users where name ~* '[a-z0-9\.\-\_]+@[a-z0-9\-]+\.[a-z]{2,5}';
id | name
----+-------------------
7 | [email protected]
8 | [email protected]
De même, nous pouvons récupérer certains noms qui ont un espace entre les deux
#select * from users where name ~* '[a-z]+\s[a-z]+';
id | name
----+----------------
2 | Jon Snow
5 | Sandip Debnath
[az] + signifie qu'il peut y avoir n'importe quelle lettre de a à z et + signifie qu'il peut se produire 1 ou plusieurs fois et\s signifie ensuite qu'il y aura un espace entre les deux, puis à nouveau un ensemble de lettres qui peuvent se produire 1 ou plus fois.
J'espère que cette analyse détaillée vous aidera.
Oui, il représente l'expression régulière POSIX. Une autre alternative consiste à utiliser l'approche standard SQL pour les expressions régulières avec l'opérateur "SIMILAR TO", bien qu'il fournisse un ensemble de fonctionnalités plus limité, pourrait être plus facile à comprendre. Je pense que c'est une bonne référence de dba exchange: https://dba.stackexchange.com/questions/10694/pattern-matching-with-like-similar-to-or-regular-expressions-in-postgresql