Musiałem napisać proste zapytanie, w którym szukałem nazwisk osób zaczynających się od B lub D:
SELECT s.name FROM spelers s WHERE s.name LIKE "B%" OR s.name LIKE "D%" ORDER BY 1
Zastanawiałem się, czy istnieje sposób na przepisanie tego, aby było bardziej wydajne. Mogę więc uniknąć or
i / lub like
?
Komentarze
- Dlaczego próbujesz przepisać? Wydajność? Czystość? Czy
s.name
jest zindeksowany? - Chcę pisać dla wydajności, s.name nie jest indeksowany.
- Cóż podczas wyszukiwania bez znaków wieloznacznych na początku i bez wybierania żadnych dodatkowych kolumn, indeks na
name
może być tutaj przydatny, jeśli zależy Ci na wydajności.
Odpowiedź
Twoje zapytanie jest prawie optymalne. Składnia nie będzie dużo krótsza, zapytanie nie będzie dużo szybsze:
SELECT name FROM spelers WHERE name LIKE "B%" OR name LIKE "D%" ORDER BY 1;
Jeśli y naprawdę chcesz skrócić składnię , użyj wyrażenia regularnego z gałęziami :
... WHERE name ~ "^(B|D).*"
Lub trochę szybciej, z klasą znaków :
... WHERE name ~ "^[BD].*"
Szybki test bez indeksu daje szybsze wyniki niż w przypadku SIMILAR TO
w obu przypadkach dla mnie.
Z odpowiednim indeksem B-Tree na miejscu, LIKE
wygrywa ten wyścig pod względem wielkości.
Przeczytaj podstawy o dopasowywaniu wzorców w instrukcji .
Indeks zapewniający najwyższą wydajność
Jeśli obawiasz się z wydajnością utwórz taki indeks dla większych tabel:
CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);
Sprawia, że tego rodzaju zapytania są szybsze o rząd wielkości. Specjalne uwagi dotyczą kolejności sortowania specyficznej dla lokalizacji. Przeczytaj więcej o klasach operatorów w instrukcji . Jeśli używasz standardowego ” C ” ustawień regionalnych (większość ludzi tego nie robi), zwykły indeks (z domyślną klasą operatora) będzie do.
Taki indeks jest dobry tylko dla wzorców zakotwiczonych w lewo (pasujących od początku ciągu).
SIMILAR TO
lub wyrażenia regularne z podstawowymi wyrażeniami zakotwiczonymi w lewo również mogą używać tego indeksu. Ale nie z gałęziami (B|D)
lub klasami znaków [BD]
(przynajmniej w moich testach na PostgreSQL 9.0).
Dopasowania trygramu lub wyszukiwanie tekstu używają specjalnych indeksów GIN lub GiST.
Przegląd operatorów dopasowywania wzorców
-
LIKE
(~~
) jest prosta i szybkie, ale ograniczone w swoich możliwościach.
ILIKE
(~~*
) wariant bez rozróżniania wielkości liter.
pg_trgm rozszerza obsługę indeksów dla obu. -
~
(dopasowanie wyrażenia regularnego) jest potężne, ale bardziej złożone i może działać wolno w przypadku czegokolwiek więcej niż podstawowych wyrażeń. -
SIMILAR TO
jest po prostu bezcelowe . Osobliwy półrasaLIKE
i wyrażeń regularnych. Nigdy tego nie używam. Zobacz poniżej. -
% to operator ” podobieństwa ” udostępniany przez dodatkowy moduł
pg_trgm
. Zobacz poniżej. -
@@
to operator wyszukiwania tekstu. Zobacz poniżej.
pg_trgm – dopasowanie trygramu
Zaczynając od PostgreSQL 9.1 możesz ułatwić rozszerzenie pg_trgm
, aby zapewnić obsługę indeksu dla dowolnego LIKE
/ ILIKE
wzorzec (i proste wzorce regexp z ~
) za pomocą Indeks GIN lub GiST.
Szczegóły, przykład i linki:
pg_trgm
udostępnia także te operatory :
-
%
– ” podobieństwo ” operator -
<%
(commutator:%>
) – the ” word_similarity w Postgres 9.6 lub nowszym -
<<%
(commutator:%>>
) – the ” strict_word_similarity ” operator w Postgres 11 lub nowszym
Wyszukiwanie tekstowe
Jest to specjalny typ dopasowywania wzorców z oddzielnymi typami infrastruktury i indeksów. Korzysta ze słowników i słowników oraz jest doskonałym narzędziem do znajdowania słów w dokumentach, zwłaszcza w przypadku języków naturalnych.
Dopasowanie prefiksu jest również obsługiwany:
Oprócz wyszukiwania frazy od Postgres 9.6:
Weź pod uwagę wprowadzenie w podręczniku i przegląd operatorów i funkcji .
Dodatkowe narzędzia do rozmytego dopasowywania łańcuchów
Dodatkowy moduł fuzzystrmatch oferuje więcej opcji, ale wydajność jest generalnie gorsza od wszystkich powyższych.
W szczególności różne implementacje funkcji levenshtein()
mogą być instrumentalne.
Dlaczego wyrażenia regularne (~
) są zawsze szybsze niż SIMILAR TO
?
Odpowiedź jest prosta. Wyrażenia SIMILAR TO
są wewnętrznie przepisywane na wyrażenia regularne. Tak więc dla każdego wyrażenia SIMILAR TO
istnieje co najmniej jedno szybsze wyrażenie regularne (co oszczędza narzut związany z przepisywaniem wyrażenia). Używanie SIMILAR TO
kiedykolwiek nie daje żadnego wzrostu wydajności.
A proste wyrażenia, które można wykonać za pomocą LIKE
(~~
), są szybsze dzięki LIKE
w każdym razie.
SIMILAR TO
jest obsługiwany tylko w PostgreSQL, ponieważ znalazł się we wczesnych wersjach standardu SQL. Nadal się go nie pozbyli. Ale są plany, aby go usunąć i zamiast tego dołączyć dopasowania regexp – a przynajmniej tak słyszałem.
EXPLAIN ANALYZE
to ujawnia. Po prostu spróbuj samodzielnie z dowolną tabelą!
EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO "B%";
Ujawnia:
... Seq Scan on spelers (cost= ... Filter: (name ~ "^(?:B.*)$"::text)
SIMILAR TO
został przepisany za pomocą wyrażenia regularnego (~
).
Najwyższa wydajność w tym konkretnym przypadku
Ale EXPLAIN ANALYZE
ujawnia więcej. Spróbuj, mając wspomniany indeks na miejscu:
EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ "^B.*;
Ujawnia:
... -> 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))
Wewnętrznie, z indeksem, który nie obsługuje ustawień regionalnych (text_pattern_ops
lub używając ustawień regionalnych C
) proste wyrażenia zakotwiczone w lewo są przepisywane przy użyciu tych operatorów wzorców tekstowych: ~>=~
, ~<=~
, ~>~
, ~<~
. Tak jest w przypadku ~
, ~~
lub SIMILAR TO
podobnie.
To samo dotyczy indeksów typów varchar
z varchar_pattern_ops
lub char
z bpchar_pattern_ops
.
Zastosowano więc do pierwotnego pytania, oto najszybszy możliwy sposób :
SELECT name FROM spelers WHERE name ~>=~ "B" AND name ~<~ "C" OR name ~>=~ "D" AND name ~<~ "E" ORDER BY 1;
Oczywiście, jeśli zdarzy Ci się wyszukać sąsiednie inicjały , możesz jeszcze bardziej uprościć:
WHERE name ~>=~ "B" AND name ~<~ "D" -- strings starting with B or C
Zysk w porównaniu z zwykłym użyciem ~
lub ~~
jest niewielki. Jeśli wydajność nie jest Twoim najważniejszym wymaganiem, powinieneś po prostu trzymać się standardowych operatorów – dochodząc do tego, co już masz w pytaniu.
Komentarze
Odpowiedź
Co powiesz na dodanie kolumny do stół. W zależności od twoich rzeczywistych wymagań:
person_name_start_with_B_or_D (Boolean) person_name_start_with_char CHAR(1) person_name_start_with VARCHAR(30)
PostgreSQL nie obsługuje kolumn obliczonych w tabelach podstawowych a la SQL Server , ale nowa kolumna może być utrzymywana przez wyzwalacz. Oczywiście ta nowa kolumna byłaby indeksowana.
Alternatywnie indeks w wyrażeniu dałoby to samo, tańsze. Np .:
CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1));
Zapytania, które pasują do wyrażenia w ich warunkach, mogą wykorzystywać ten indeks.
W ten sposób wynik wydajności jest brany pod uwagę, gdy dane są tworzone lub zmieniane, więc może być odpowiednie tylko dla środowiska o niskiej aktywności (tj. znacznie mniej zapisów niż odczytów).
Odpowiedź
Możesz spróbować
SELECT s.name FROM spelers s WHERE s.name SIMILAR TO "(B|D)%" ORDER BY s.name
Nie mam pojęcia, czy powyższe lub twoje oryginalne wyrażenie można rozpatrzyć w Postgresie.
Jeśli utworzysz sugerowany indeks, chciałbym również usłyszeć, jak to porównuje się z innymi opcjami.
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
Komentarze
- Udało się i mam koszt 1.19, gdzie miałem 1,25. Dzięki!
Odpowiedź
To, co robiłem w przeszłości, mając podobny problem z wydajnością, to zwiększ znak ASCII ostatniej litery i wykonaj BETWEEN. Otrzymujesz wtedy najlepszą wydajność dla podzbioru funkcji LIKE. Oczywiście działa to tylko w niektórych sytuacjach, ale w przypadku bardzo dużych zbiorów danych, w których „poszukujesz na przykład nazwy, wydajność przechodzi od fatalnej do akceptowalnej.
Odpowiedź
Bardzo stare pytanie, ale znalazłem inne szybkie rozwiązanie tego problemu:
SELECT s.name FROM spelers s WHERE ascii(s.name) in (ascii("B"),ascii("D")) ORDER BY 1
Ponieważ funkcja ascii ( ) patrzy tylko na pierwszy znak ciągu.
Komentarze
- Czy to używa indeksu na
(name)
?
Odpowiedź
Aby sprawdzić inicjały, często używam rzutowania na "char"
(z podwójnymi cudzysłowami). Nie jest przenośny, ale bardzo szybki. Wewnętrznie po prostu usuwa przywiązanie do tekstu i zwraca pierwszy znak, a operacje porównania „char” są bardzo szybkie, ponieważ typ ma stałą długość 1-bajtową:
SELECT s.name FROM spelers s WHERE s.name::"char" =ANY( ARRAY[ "char" "B", "D" ] ) ORDER BY 1
Pamiętaj, że przesyłanie do "char"
jest szybsze niż ascii()
slution przez @ Sole021, ale nie jest zgodne z UTF8 (ani żadnym innym kodowaniem dla to ma znaczenie), zwracając po prostu pierwszy bajt, więc powinno być używane tylko w przypadkach, gdy porównanie dotyczy zwykłych starych 7-bitowych znaków ASCII.
Odpowiedź
Istnieją dwie niewymienione jeszcze metody radzenia sobie z takimi przypadkami:
-
indeks częściowy (lub podzielony na partycje – jeśli utworzono dla pełnego zakresu ręcznie) – najbardziej przydatny, wymagany jest tylko podzbiór danych (na przykład podczas konserwacji lub tymczasowy w przypadku niektórych raportów):
CREATE INDEX ON spelers WHERE name LIKE "B%"
-
partycjonowanie samej tabeli (używając pierwszego znaku jako klucza partycjonującego) – ta technika jest szczególnie wort h biorąc pod uwagę PostgreSQL 10+ (mniej bolesne partycjonowanie) i 11+ (czyszczenie partycji podczas wykonywania zapytania).
Ponadto, jeśli dane w tabeli są posortowane, można skorzystaj z indeksu BRIN (nad pierwszym znakiem).
Odpowiedź
Prawdopodobnie szybsze porównanie pojedynczego znaku:
SUBSTR(s.name,1,1)="B" OR SUBSTR(s.name,1,1)="D"
Komentarze
- Nie naprawdę.
column LIKE 'B%'
będzie bardziej wydajne niż użycie funkcji podciągów w kolumnie.
similar
skan?EXPLAIN ANALYZE
pokazuje 2 skany indeksu bitmap.Wiele skanów indeksów bitmap można połączyć dość szybko.OR
naUNION ALL
lub zamianyname LIKE 'B%'
naname >= 'B' AND name <'C'
w Postgres?UNION
won ' t ale tak, połączenie zakresów w jedną klauzulęWHERE
przyspieszy zapytanie. Dodałem więcej do mojej odpowiedzi. Oczywiście musisz wziąć pod uwagę swoje ustawienia regionalne. Wyszukiwanie z uwzględnieniem lokalizacji jest zawsze wolniejsze.