it-swarm-eu.dev

Shoda vzorů s LIKE, SIMILAR TO nebo regulárními výrazy v PostgreSQL

Musel jsem napsat jednoduchý dotaz, kam jdu hledat jméno lidí začínající písmenem B nebo D:

SELECT s.name 
FROM spelers s 
WHERE s.name LIKE 'B%' OR s.name LIKE 'D%'
ORDER BY 1

Přemýšlel jsem, jestli existuje způsob, jak to přepsat, aby se stal výkonnějším. Takže se mohu vyhnout or a/nebo like?

103
Lucas Kauffman

Váš dotaz je do značné míry optimální. Syntaxe nebude mnohem kratší, dotaz nebude mnohem rychlejší:

SELECT name
FROM   spelers
WHERE  name LIKE 'B%' OR name LIKE 'D%'
ORDER  BY 1;

Pokud opravdu chcete zkrátit syntaxi , použijte regulární výraz s větvemi :

...
WHERE  name ~ '^(B|D).*'

Nebo o něco rychlejší s třídou znaků :

...
WHERE  name ~ '^[BD].*'

Rychlý test bez indexu přináší rychlejší výsledky než pro SIMILAR TO V obou případech pro mě.
Se správným indexem B-Tree, LIKE vyhraje tento závod o řádovou velikost.

Přečtěte si základní informace o přizpůsobení vzoru v příručce .

Index pro vynikající výkon

Pokud se zajímáte o výkon, vytvořte takový index pro větší tabulky:

CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);

Dělá tento druh dotazu rychleji podle velikosti. Zvláštní pořadí platí pro pořadí řazení podle místních nastavení. Přečtěte si více o třídy operátorů v manuál . Pokud používáte standardní národní prostředí „C“ (většina lidí ne), provede se prostý index (s výchozí třídou operátorů).

Takový index je vhodný pouze pro vzory ukotvené vlevo (shodné od začátku řetězce).

SIMILAR TO Nebo regulární výrazy se základními vlevo ukotvenými výrazy mohou také použít tento index. Ale ne s větvemi (B|D) Nebo třídami znaků [BD] (Alespoň v mých testech na PostgreSQL 9.0).

Trigramové shody nebo textové vyhledávání používají speciální indexy GIN nebo Gist.

Přehled operátorů odpovídajících vzorům

  • LIKE (~~) je jednoduchý a rychlý, ale má omezené možnosti.
    ILIKE (~~*) varianta necitlivá na velikost písmen.
    pg_trgm rozšiřuje podporu indexu pro oba.

  • ~ (shoda s regulárním výrazem) je mocný, ale složitější a může být pomalý u všeho jiného než základního výrazy.

  • SIMILAR TO je jen zbytečné . Zvláštní polokrm LIKE a regulárních výrazů. Nikdy to nepoužívám. Viz. níže.

  • % je operátor „podobnosti“ poskytovaný dalším modulem pg_trgm. Viz. níže.

  • @@ je operátor textového vyhledávání. Viz. níže.

pg_trgm - shoda s trigramy

Počínaje PostgreSQL 9.1 můžete rozšířením pg_trgm poskytnout podporu indexu jakýkoli LIKE/ILIKE vzor (a jednoduché regexp vzory s ~) pomocí indexu GIN nebo Gist.

Podrobnosti, příklad a odkazy:

pg_trgm Také poskytuje tito operátoři :

  • % - operátor „podobnosti“
  • <% (komutátor: %>)) - operátor „Word_similarity“ v Postgres 9.6 nebo novější
  • <<% (komutátor: %>>) - operátor „strict_Word_similarity“ v Postgresu 11 nebo novějším

Textové vyhledávání

Je speciální typ vzoru, který odpovídá samostatným typům infrastruktury a indexů. Používá slovníky a prameny a je skvělým nástrojem k nalezení slov v dokumentech, zejména pro přirozené jazyky.

Shoda předpony je také podporována:

Stejně jako vyhledávání frází od Postgresu 9.6:

Zvažte úvod v manuál a přehled operátorů a funkcí .

Další nástroje pro fuzzy párování řetězců

Další modul fuzzystrmatch nabízí několik dalších možností, ale výkon je obecně nižší než u všech výše uvedených.

Zejména mohou být pomocné různé implementace funkce levenshtein().

Proč jsou regulární výrazy (~) Vždy rychlejší než SIMILAR TO?

Odpověď je jednoduchá. SIMILAR TO Výrazy jsou interně přepsány na regulární výrazy. Takže pro každý výraz SIMILAR TO Existuje alespoň jeden rychlejší regulární výraz (který šetří režii přepisování výrazu). Při použití SIMILAR TO nikdy nedochází ke zvýšení výkonu.

A jednoduché výrazy, které lze pomocí LIKE (~~), Jsou s LIKE rychlejší.

SIMILAR TO Je podporován pouze v PostgreSQL, protože skončil v raných verzích standardu SQL. Stále se toho nezbavili. Ale existují plány na jeho odstranění a zahrnutí regexp zápasů místo toho - nebo tak jsem slyšel.

EXPLAIN ANALYZE Odhaluje. Jen zkuste s jakýmkoli stolem sami!

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO 'B%';

Odhaluje:

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

SIMILAR TO Byl přepsán regulárním výrazem (~).

Konečný výkon pro tento konkrétní případ

Ale EXPLAIN ANALYZE Odhaluje více. Zkuste s výše uvedeným indexem:

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ '^B.*;

Odhaluje:

...
 ->  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))

Interně, s indexem, který nepoznává národní prostředí (text_pattern_ops Nebo používá národní prostředí C), jsou jednoduché výrazy ukotvené vlevo přepsány těmito operátory textových vzorů: ~>=~, ~<=~, ~>~, ~<~. Toto je případ pro ~, ~~ Nebo SIMILAR TO.

Totéž platí pro indexy typů varchar s varchar_pattern_ops Nebo char with bpchar_pattern_ops.

Takže při použití na původní otázku je to nejrychlejší možný způsob :

SELECT name
FROM   spelers  
WHERE  name ~>=~ 'B' AND name ~<~ 'C'
    OR name ~>=~ 'D' AND name ~<~ 'E'
ORDER  BY 1;

Samozřejmě, pokud byste náhodou hledali sousedící iniciály , můžete je dále zjednodušit:

WHERE  name ~>=~ 'B' AND name ~<~ 'D'   -- strings starting with B or C

Zisk z prostého použití ~ Nebo ~~ Je malý. Pokud výkon není vaším nejdůležitějším požadavkem, měli byste se držet standardních operátorů - dospět k tomu, co již v otázce máte.

171

Jak se o přidání sloupce do tabulky. Podle vašich skutečných požadavků:

person_name_start_with_B_or_D (Boolean)

person_name_start_with_char CHAR(1)

person_name_start_with VARCHAR(30)

PostgreSQL nepodporuje vypočítané sloupce v základních tabulkách a la SQL Server , ale nový sloupec lze udržovat pomocí triggeru. Je zřejmé, že tento nový sloupec bude indexován.

Alternativně index výraz vám poskytne stejné, levnější. Např.:

CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1)); 

Tento index mohou využít dotazy, které odpovídají jejich podmínkám.

Tímto způsobem se výkonnostní přístup získá, když jsou data vytvořena nebo změněna, takže může být vhodný pouze pro prostředí s nízkou aktivitou (tj. Mnohem méně zápisů než přečtení).

11
onedaywhen

Můžete zkuste

SELECT s.name
FROM   spelers s
WHERE  s.name SIMILAR TO '(B|D)%' 
ORDER  BY s.name

Nemám tušení, zda jsou výše uvedený nebo váš původní výraz v Postgresu sargable.

Pokud vytvoříte navrhovaný index, bude také zajímat, jak se to srovnává s ostatními možnostmi.

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
8
Martin Smith

Pro kontrolu iniciál často používám casting na "char" (S dvojitými uvozovkami). Není přenosný, ale velmi rychlý. Interně jednoduše odstraní text a vrátí první znak a operace porovnávání znaků jsou velmi rychlé, protože typ je 1 bajt pevná délka:

SELECT s.name 
FROM spelers s 
WHERE s.name::"char" =ANY( ARRAY[ "char" 'B', 'D' ] )
ORDER BY 1

Mějte na paměti, že obsazení do "char" Je rychlejší než dělení ascii() @ @ Sole021, ale není kompatibilní s UTF8 (nebo jakýmkoli jiným kódováním), vrací pouze první bajt, takže by měl použít pouze v případech, kdy je porovnání oproti obyčejným starým 7bitovým znakům ASCII znaků).

Velmi stará otázka, ale našel jsem další rychlé řešení tohoto problému:

SELECT s.name 
FROM spelers s 
WHERE ascii(s.name) in (ascii('B'),ascii('D'))
ORDER BY 1

Protože funkce ascii () sleduje pouze první znak řetězce.

2
Sole021

Co jsem udělal v minulosti, když jsem čelil podobnému problému s výkonem, je zvýšit znak ASCII=) posledního písmene a udělat MEZI. Pak získáte nejlepší výkon pro podmnožinu funkčnosti LIKE. Samozřejmě to funguje pouze v určitých situacích, ale v případě extrémně velkých datových souborů, kde například vyhledáváte jméno, se výkon mění z propastného na přijatelný.

2
Mel Padden

Existují dvě metody, které ještě nejsou uvedeny pro řešení takových případů:

  1. částečný (nebo rozdělený na oddíly - pokud je vytvořen ručně pro celý rozsah) - nejužitečnější, když je vyžadována pouze podmnožina dat (například během určité údržby nebo dočasná pro některé zprávy):

    CREATE INDEX ON spelers WHERE name LIKE 'B%'
    
  2. rozdělení tabulky samotné (použití prvního znaku jako rozdělovacího klíče) - tuto techniku ​​stojí za zvážení v PostgreSQL 10+ (méně bolestivé rozdělení) a 11+ (ořezávání oddílů během provádění dotazu).

Navíc, pokud jsou data v tabulce tříděna, lze těžit z použití index BRIN (přes první znak).

1
Tomasz Pala