Ik moest een eenvoudige vraag schrijven waarin ik op zoek ga naar de naam van mensen die beginnen met een B of een D:
SELECT s.name FROM spelers s WHERE s.name LIKE "B%" OR s.name LIKE "D%" ORDER BY 1
Ik vroeg me af of er een manier is om dit te herschrijven om beter te presteren. Dus ik kan en / of like
?
Reacties
- Waarom probeer je herschrijven? Prestaties? Netheid? Is
s.name
geïndexeerd? - Ik wil schrijven voor prestaties, s.name wordt niet geïndexeerd.
- Nou aangezien u zoekt zonder leidende jokertekens en geen extra kolommen selecteert, kan een index op
name
hier handig zijn als u om prestaties geeft.
Antwoord
Uw vraag is vrijwel optimaal. Syntaxis wordt niet veel korter, vraag wordt niet veel sneller:
SELECT name FROM spelers WHERE name LIKE "B%" OR name LIKE "D%" ORDER BY 1;
Als y je wilt echt de syntaxis verkorten, een reguliere expressie gebruiken met branches :
... WHERE name ~ "^(B|D).*"
Of een beetje sneller, met een character class :
... WHERE name ~ "^[BD].*"
Een snelle test zonder index levert snellere resultaten op dan voor SIMILAR TO
in beide gevallen voor mij.
Met een geschikte B-Tree-index op zijn plaats, LIKE
wint deze race in ordes van grootte.
Lees de basisprincipes van patroonovereenkomst in de handleiding .
Index voor superieure prestaties
Als u zich zorgen maakt met prestaties, maak een index zoals deze voor grotere tabellen:
CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);
Maakt dit soort zoekopdrachten sneller met ordes van grootte. Er gelden speciale overwegingen voor de locale-specifieke sorteervolgorde. Lees meer over operatorklassen in de handleiding . Als u de standaard ” C ” landinstelling gebruikt (de meeste mensen niet), zal een gewone index (met de standaard operatorklasse) do.
Zon index is alleen goed voor links verankerde patronen (matching vanaf het begin van de string).
SIMILAR TO
of reguliere expressies met links verankerde basisexpressies kunnen deze index ook gebruiken. Maar niet met vertakkingen (B|D)
of tekenklassen [BD]
(tenminste in mijn tests op PostgreSQL 9.0).
Trigram-overeenkomsten of tekstzoekopdrachten gebruiken speciale GIN- of GiST-indexen.
Overzicht van operatoren voor patroonovereenkomst
-
LIKE
(~~
) is eenvoudig en snel maar beperkt in zijn mogelijkheden.
ILIKE
(~~*
) de niet hoofdlettergevoelige variant.
pg_trgm breidt indexondersteuning uit voor beide. -
~
(overeenkomst met reguliere expressies) is krachtig maar complexer en kan traag zijn voor meer dan eenvoudige expressies. -
SIMILAR TO
is gewoon zinloos . Een eigenaardige halfbreeding vanLIKE
en reguliere expressies. Ik gebruik het nooit. Zie hieronder. -
% is de ” overeenkomst ” operator, geleverd door de aanvullende module
pg_trgm
. Zie hieronder. -
@@
is de tekstzoekoperator. Zie hieronder.
pg_trgm – trigram-matching
Beginnend met PostgreSQL 9.1 u kunt de extensie pg_trgm
faciliteren om indexondersteuning te bieden voor elke LIKE
/ ILIKE
patroon (en eenvoudige regexp-patronen met ~
) met een GIN- of GiST-index.
Details, voorbeeld en links:
pg_trgm
biedt ook deze operatoren :
-
%
– de ” overeenkomst ” operator -
<%
(commutator:%>
) – het ” word_similarity ” -operator in Postgres 9.6 of hoger -
<<%
(commutator:%>>
) – de ” strict_word_similarity ” operator in Postgres 11 of later
Tekst zoeken
Is een speciaal type patroonovereenkomst met afzonderlijke infrastructuur- en indextypes. Het maakt gebruik van woordenboeken en stamnamen en is een geweldig hulpmiddel om woorden in documenten te vinden, vooral voor natuurlijke talen.
Voorvoegsels matchen wordt ook ondersteund:
Evenals zoeken op woordgroep sinds Postgres 9.6:
Overweeg de introductie in de handleiding en het overzicht van operatoren en functies .
Extra tools voor het matchen van vage tekenreeksen
De aanvullende module fuzzystrmatch biedt wat meer opties, maar de prestaties zijn over het algemeen slechter dan al het bovenstaande.
Met name verschillende implementaties van de levenshtein()
-functie kunnen een belangrijke rol spelen.
Waarom zijn reguliere expressies (~
) altijd sneller dan SIMILAR TO
?
Het antwoord is simpel. SIMILAR TO
expressies worden intern herschreven in reguliere expressies. Dus voor elke SIMILAR TO
expressie is er minstens één snellere reguliere expressie (dat bespaart de overhead van het herschrijven van de expressie). Er is geen prestatiewinst bij het gebruik van SIMILAR TO
ooit .
En eenvoudige uitdrukkingen die kunnen worden gedaan met LIKE
(~~
) zijn sneller met LIKE
hoe dan ook.
SIMILAR TO
wordt alleen ondersteund in PostgreSQL omdat het in vroege versies van de SQL-standaard terechtkwam. Ze zijn er nog steeds niet van af. Maar er zijn plannen om het te verwijderen en in plaats daarvan regexp-overeenkomsten op te nemen – zo hoorde ik.
EXPLAIN ANALYZE
onthult het. Probeer het gewoon zelf met een willekeurige tafel!
EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO "B%";
Onthult:
... Seq Scan on spelers (cost= ... Filter: (name ~ "^(?:B.*)$"::text)
SIMILAR TO
is herschreven met een reguliere expressie (~
).
Ultieme prestaties voor dit specifieke geval
Maar EXPLAIN ANALYZE
onthult meer. Probeer, met de bovengenoemde index op zijn plaats:
EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ "^B.*;
Onthult:
... -> 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, met een index die niet locale-bewust is (text_pattern_ops
of met locale C
) eenvoudige links verankerde expressies worden herschreven met deze tekstpatroonoperatoren: ~>=~
, ~<=~
, ~>~
, ~<~
. Dit is het geval voor ~
, ~~
of SIMILAR TO
gelijk.
Hetzelfde geldt voor indexen op varchar
typen met varchar_pattern_ops
of char
met bpchar_pattern_ops
.
Dus, toegepast op de oorspronkelijke vraag, dit is de snelst mogelijke manier :
SELECT name FROM spelers WHERE name ~>=~ "B" AND name ~<~ "C" OR name ~>=~ "D" AND name ~<~ "E" ORDER BY 1;
Als u toevallig zoekt naar aangrenzende initialen , kunt u natuurlijk verder vereenvoudigen:
WHERE name ~>=~ "B" AND name ~<~ "D" -- strings starting with B or C
De winst ten opzichte van gewoon gebruik van ~
of ~~
is klein. Als prestatie niet uw belangrijkste vereiste is, moet u gewoon bij de standaardoperatoren blijven – om te komen tot wat u al in de vraag heeft.
Opmerkingen
Answer
Hoe zit het met het toevoegen van een kolom aan de tafel. Afhankelijk van uw daadwerkelijke vereisten:
person_name_start_with_B_or_D (Boolean) person_name_start_with_char CHAR(1) person_name_start_with VARCHAR(30)
PostgreSQL ondersteunt niet berekend kolommen in basistabellen a la SQL Server , maar de nieuwe kolom kan worden onderhouden via trigger. Het is duidelijk dat deze nieuwe kolom zou worden geïndexeerd.
Als alternatief kan een index op een uitdrukking zou u hetzelfde, goedkoper opleveren. Bijvoorbeeld:
CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1));
Querys die overeenkomen met de uitdrukking in hun voorwaarden, kunnen deze index gebruiken.
Op deze manier wordt de prestatiehit genomen wanneer de gegevens worden gemaakt of gewijzigd, dus mogelijk alleen geschikt voor een omgeving met weinig activiteit (dwz veel minder schrijfbewerkingen dan leesbewerkingen).
Antwoord
U kunt proberen
SELECT s.name FROM spelers s WHERE s.name SIMILAR TO "(B|D)%" ORDER BY s.name
Ik heb geen idee of het bovenstaande of je originele uitdrukking al dan niet sargable zijn in Postgres.
Als je de voorgestelde index maakt, zou ik ook graag willen horen hoe dit is vergelijkbaar met de andere opties.
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
Reacties
- Het werkte en ik kreeg een prijs van 1,19 waar ik 1,25 had. Bedankt!
Antwoord
Wat ik in het verleden heb gedaan, geconfronteerd met een soortgelijk prestatieprobleem, is verhoog het ASCII-teken van de laatste letter, en doe een TUSSEN. U krijgt dan de beste prestaties voor een subset van de LIKE-functionaliteit. Het werkt natuurlijk alleen in bepaalde situaties, maar voor ultragrote datasets waarbij je bijvoorbeeld op een naam zoekt, worden de prestaties van erbarmelijk naar acceptabel.
Antwoord
Zeer oude vraag, maar ik vond nog een snelle oplossing voor dit probleem:
SELECT s.name FROM spelers s WHERE ascii(s.name) in (ascii("B"),ascii("D")) ORDER BY 1
Sinds function ascii ( ) kijkt alleen naar het eerste teken van de string.
Reacties
- Gebruikt dit een index op
(name)
?
Answer
Voor het controleren van initialen gebruik ik vaak casten naar "char"
(met dubbele aanhalingstekens) Het is niet draagbaar, maar erg snel. Intern verwijdert het eenvoudig de tekst en retourneert het eerste teken, en “char” -vergelijkingsbewerkingen zijn erg snel omdat het type een vaste lengte van 1 byte heeft:
SELECT s.name FROM spelers s WHERE s.name::"char" =ANY( ARRAY[ "char" "B", "D" ] ) ORDER BY 1
Houd er rekening mee dat casten naar "char"
sneller is dan de ascii()
-slutie van @ Sole021, maar het is niet UTF8-compatibel (of enige andere codering voor dat er toe doet), en alleen de eerste byte retourneert, dus zou alleen moeten worden gebruikt in gevallen waarin de vergelijking plaatsvindt met gewone oude 7-bits ASCII-tekens.
Answer
Er zijn twee methoden die nog niet zijn genoemd om met dergelijke gevallen om te gaan:
-
gedeeltelijke (of gepartitioneerde – indien handmatig aangemaakt voor volledig bereik) index – zeer nuttig wanneer slechts een subset van gegevens is vereist (bijvoorbeeld tijdens onderhoud of tijdelijk voor sommige rapportages):
CREATE INDEX ON spelers WHERE name LIKE "B%"
-
de tabel zelf partitioneren (met het eerste teken als partitiesleutel) – deze techniek is vooral wort h overwegen in PostgreSQL 10+ (minder pijnlijke partitionering) en 11+ (partitie opschonen tijdens het uitvoeren van querys).
Bovendien, als de gegevens in een tabel zijn gesorteerd, kan men profiteren van het gebruik van BRIN-index (boven het eerste teken).
Antwoord
Waarschijnlijk sneller om één teken te vergelijken:
SUBSTR(s.name,1,1)="B" OR SUBSTR(s.name,1,1)="D"
Reacties
- Niet werkelijk.
column LIKE 'B%'
zal efficiënter zijn dan het gebruik van de substring-functie op de kolom.
similar
een scan?EXPLAIN ANALYZE
toont 2 bitmapindexscans.Meerdere bitmapindexscans kunnen vrij snel worden gecombineerd.OR
te vervangen doorUNION ALL
of doorname LIKE 'B%'
te vervangen doorname >= 'B' AND name <'C'
in Postgres?UNION
won ‘ t maar ja, het combineren van de bereiken in éénWHERE
-clausule zal de zoekopdracht versnellen. Ik heb meer aan mijn antwoord toegevoegd. Je moet natuurlijk rekening houden met je locale. Zoeken op locatiebewust is altijd langzamer.