Jeg måtte skrive et enkelt spørsmål der jeg går og leter etter personens navn som starter med et B eller en D:
SELECT s.name FROM spelers s WHERE s.name LIKE "B%" OR s.name LIKE "D%" ORDER BY 1
Jeg lurte på om det er en måte å omskrive dette for å bli mer performant. Så jeg kan unngå or
og / eller like
?
Kommentarer
- Hvorfor prøver du å omskrive? Ytelse? Ryddighet? Er
s.name
indeksert? - Jeg vil skrive for ytelse, s.name er ikke indeksert.
- Vel når du søker uten å føre jokertegn og ikke velger noen ekstra kolonner, kan en indeks på
name
være nyttig her hvis du bryr deg om ytelsen.
Svar
Søket ditt er stort sett det optimale. Syntaksen blir ikke kortere, spørringen blir ikke mye raskere:
SELECT name FROM spelers WHERE name LIKE "B%" OR name LIKE "D%" ORDER BY 1;
Hvis y du vil virkelig forkorte syntaksen , bruk et vanlig uttrykk med grener :
... WHERE name ~ "^(B|D).*"
Eller litt raskere, med en karakterklasse :
... WHERE name ~ "^[BD].*"
En rask test uten indeks gir raskere resultater enn for SIMILAR TO
i begge tilfeller for meg.
Med en passende B-treindeks på plass, vinner LIKE
dette løpet etter størrelsesorden.
Les det grunnleggende om mønstermatching i håndboken .
Indeks for overlegen ytelse
Hvis du er bekymret med ytelse, lag en indeks som denne for større tabeller:
CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);
Gjør denne typen spørsmål raskere etter størrelsesorden. Spesielle hensyn gjelder for stedsspesifikk sorteringsrekkefølge. Les mer om operatørklasser i håndboken . Hvis du bruker standard » C » sted (de fleste ikke gjør det), vil en vanlig indeks (med standard operatørklasse) gjør.
En slik indeks er bare bra for venstreforankrede mønstre (matching fra begynnelsen av strengen).
SIMILAR TO
eller regulære uttrykk med grunnleggende venstreankrede uttrykk kan også bruke denne indeksen. Men ikke med grener (B|D)
eller tegnklasser [BD]
(i det minste i testene mine på PostgreSQL 9.0).
Trigram-treff eller tekstsøk bruker spesielle GIN- eller GiST-indekser.
Oversikt over mønstermatchende operatører
-
LIKE
(~~
) er enkelt og raskt, men begrenset i dets evner.
ILIKE
(~~*
) den saksfølsomme varianten.
pg_trgm utvider indeksstøtte for begge. -
~
(samsvar med vanlig uttrykk) er kraftig, men mer kompleks og kan være treg for alt mer enn grunnleggende uttrykk. -
SIMILAR TO
er bare meningsløst . En merkelig halvras avLIKE
og vanlige uttrykk. Jeg bruker den aldri. Se nedenfor. -
% er » likhet » operatør, levert av tilleggsmodulen
pg_trgm
. Se nedenfor. -
@@
er tekstsøkoperatøren. Se nedenfor.
pg_trgm – trigram matching
Begynner med PostgreSQL 9.1 du kan legge til rette for utvidelsen pg_trgm
for å gi indeksstøtte for alle LIKE
/ ILIKE
mønster (og enkle regexp-mønstre med ~
) ved hjelp av en GIN- eller GiST-indeks.
Detaljer, eksempel og lenker:
pg_trgm
gir også disse operatørene :
-
%
– » likhet » operatør -
<%
(kommutator:%>
) – » word_similarity » operator i Postgres 9.6 eller senere -
<<%
(kommutator:%>>
) – » strict_word_similarity » operatør i Postgres 11 eller senere
Tekstsøk
Er en spesiell type mønstermatching med separate infrastruktur- og indeksetyper. Den bruker ordbøker og stemming og er et flott verktøy for å finne ord i dokumenter, spesielt for naturlige språk.
Prefiks som samsvarer med støttes også:
Samt frasesøk siden Postgres 9.6:
Vurder introduksjon i håndboken og oversikten over operatører og funksjoner .
Ekstra verktøy for fuzzy strengmatching
Tilleggsmodulen fuzzystrmatch tilbyr noen flere alternativer, men ytelsen er generelt dårligere enn alle de ovennevnte.
Spesielt forskjellige implementeringer av levenshtein()
-funksjonen kan være medvirkende.
Hvorfor er vanlige uttrykk (~
) alltid raskere enn SIMILAR TO
?
Svaret er enkelt. SIMILAR TO
uttrykk skrives om til regulære uttrykk internt. Så for hvert SIMILAR TO
uttrykk er det minst ett raskere vanlig uttrykk (som sparer overhead for å omskrive uttrykket). Det er ingen ytelsesgevinst ved å bruke SIMILAR TO
noensinne .
Og enkle uttrykk som kan gjøres med LIKE
(~~
) er raskere med LIKE
uansett.
SIMILAR TO
støttes bare i PostgreSQL fordi det havnet i tidlige utkast til SQL-standarden. De har fortsatt ikke blitt kvitt det. Men det er planer om å fjerne det og inkludere regexp-kamper i stedet – eller så hørte jeg det.
EXPLAIN ANALYZE
avslører det. Bare prøv med hvilken som helst tabell selv!
EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO "B%";
Avslører:
... Seq Scan on spelers (cost= ... Filter: (name ~ "^(?:B.*)$"::text)
SIMILAR TO
har blitt skrevet om med et regulært uttrykk (~
).
Ultimate ytelse for denne spesielle saken
Men EXPLAIN ANALYZE
avslører mer. Prøv med ovennevnte indeks på plass:
EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ "^B.*;
Avslører:
... -> 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))
Internt, med en indeks som ikke er lokalbevisst (text_pattern_ops
eller bruker lokal C
) enkle venstreankrede uttrykk skrives om med disse tekstmønsteroperatørene: ~>=~
, ~<=~
, ~>~
, ~<~
. Dette er tilfellet for ~
, ~~
eller SIMILAR TO
likt.
Det samme gjelder indekser på varchar
typer med varchar_pattern_ops
eller char
med bpchar_pattern_ops
.
Så anvendt til det opprinnelige spørsmålet, dette er raskest mulig måte :
SELECT name FROM spelers WHERE name ~>=~ "B" AND name ~<~ "C" OR name ~>=~ "D" AND name ~<~ "E" ORDER BY 1;
Hvis du tilfeldigvis søker etter tilstøtende initialer , kan du selvfølgelig forenkle videre:
WHERE name ~>=~ "B" AND name ~<~ "D" -- strings starting with B or C
Gevinsten ved vanlig bruk av ~
eller ~~
er liten. Hvis ytelsen ikke er ditt viktigste krav, bør du bare holde deg til standardoperatørene – når du kommer til det du allerede har i spørsmålet.
Kommentarer
Svar
Hva med å legge til en kolonne i bord. Avhengig av dine faktiske krav:
person_name_start_with_B_or_D (Boolean) person_name_start_with_char CHAR(1) person_name_start_with VARCHAR(30)
PostgreSQL støtter ikke beregnede kolonner i basistabeller a la SQL Server , men den nye kolonnen kan opprettholdes via utløseren. Åpenbart vil denne nye kolonnen bli indeksert.
Alternativt, en indeks på et uttrykk vil gi deg det samme, billigere. F.eks:
CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1));
Spørringer som samsvarer med uttrykket under deres forhold, kan bruke denne indeksen.
På denne måten blir ytelsestreffet tatt når dataene blir opprettet eller endret, så det kan bare være aktuelt for et miljø med lav aktivitet (dvs. mye færre skriver enn lesninger).
Svar
Du kan prøve
SELECT s.name FROM spelers s WHERE s.name SIMILAR TO "(B|D)%" ORDER BY s.name
Jeg har ingen anelse om det ovennevnte eller det opprinnelige uttrykket ditt er sargable i Postgres. Ikke.
Hvis du oppretter den foreslåtte indeksen, vil det også være interessert i å høre hvordan dette sammenlignes med de andre alternativene.
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
Kommentarer
- Det fungerte og jeg fikk en kostnad på 1,19 hvor jeg hadde 1,25. Takk!
Svar
Det jeg har gjort tidligere, overfor et lignende ytelsesproblem, er å øk ASCII-tegnet til den siste bokstaven, og gjør en MELLOM. Du får da den beste ytelsen, for en delmengde av LIKE-funksjonaliteten. Selvfølgelig fungerer det bare i visse situasjoner, men for ultra-store datasett der du for eksempel søker på et navn, får det ytelsen til å gå fra dyp til akseptabel.
Svar
Veldig gammelt spørsmål, men jeg fant en annen rask løsning på dette problemet:
SELECT s.name FROM spelers s WHERE ascii(s.name) in (ascii("B"),ascii("D")) ORDER BY 1
Siden funksjon ascii ( ) ser bare på strengens første tegn.
Kommentarer
- Bruker dette en indeks på
(name)
?
Svar
For å sjekke initialer bruker jeg ofte casting til "char"
(med doble anførselstegn). Det er ikke bærbart, men veldig raskt. Internt, detoasts bare teksten og returnerer det første tegnet, og «char» sammenligningsoperasjoner er veldig raske fordi typen er 1 byte fast lengde:
SELECT s.name FROM spelers s WHERE s.name::"char" =ANY( ARRAY[ "char" "B", "D" ] ) ORDER BY 1
Merk at casting til "char"
er raskere enn ascii()
-slusingen av @ Sole021, men den er ikke UTF8-kompatibel (eller annen koding for den saken), returnerer bare den første byten, så skal bare brukes i tilfeller der sammenligningen er mot vanlige gamle 7-biters ASCII-tegn.
Svar
Det er to metoder som ikke er nevnt ennå for å håndtere slike tilfeller:
-
delvis (eller partisjonert – hvis opprettet for hele området manuelt) indeks – mest nyttig når bare et delsett med data kreves (for eksempel under noe vedlikehold eller midlertidig for noen rapportering):
CREATE INDEX ON spelers WHERE name LIKE "B%"
-
partisjonering av selve tabellen (bruker det første tegnet som partisjonsnøkkel) – denne teknikken er spesielt urte h vurderer i PostgreSQL 10+ (mindre smertefull partisjonering) og 11+ (beskjæring av partisjon under kjøring av spørring).
Hvis dataene i en tabell er sortert, kan man dessuten dra nytte av å bruke BRIN-indeks (over det første tegnet).
Svar
Sannsynligvis raskere å sammenligne ett tegn:
SUBSTR(s.name,1,1)="B" OR SUBSTR(s.name,1,1)="D"
Kommentarer
- Ikke egentlig.
column LIKE 'B%'
vil være mer effektiv enn å bruke understrengingsfunksjon i kolonnen.
similar
en skanning?EXPLAIN ANALYZE
viser 2 bitmap-indeksskanninger.Flere bitmap-indeksskanninger kan kombineres ganske raskt.OR
medUNION ALL
eller erstattename LIKE 'B%'
medname >= 'B' AND name <'C'
i Postgres?UNION
vant ‘ t men ja, å kombinere områdene i enWHERE
-sats vil øke spørringen. Jeg har lagt til mer i svaret mitt. Selvfølgelig må du ta hensyn til lokaliteten din. Lokalbevisst søk er alltid tregere.