PostgreSQL'de LIKE, SIMILAR TO veya normal ifadelerle eşleşen desen


94

B veya D ile başlayan insanların adını aradığım yere basit bir sorgu yazmak zorunda kaldım:

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

Bunu daha iyi performans göstermesi için yeniden yazmanın bir yolu olup olmadığını merak ediyordum. Böylece kaçabilirim orve / veya like?


Neden yeniden yazmaya çalışıyorsun? Verim? Zariflik? Is s.nameendeksli?
Martin Smith,

Performans için yazmak istiyorum, s.name dizine eklenmedi.
Lucas Kauffman,

8
Önde gelen joker karakterler olmadan arama yaptığınız ve herhangi bir ek sütun seçmeyeceğiniz için name, performansa önem veriyorsanız, burada bir endekste yararlı olabilir.
Martin Smith,

Yanıtlar:


161

Sorgunuz hemen hemen en uygunudur. Sözdizimi çok daha kısa olmayacak, sorgu çok daha hızlı olmayacak:

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

Sözdizimini gerçekten kısaltmak istiyorsanız , dallarla düzenli bir ifade kullanın :

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

Veya bir karakter sınıfıyla biraz daha hızlı :

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

İndekssiz hızlı bir test SIMILAR TO, her iki durumda da benim için olduğundan daha hızlı sonuç verir .
Uygun bir B-Tree endeksi mevcut olduğunda, LIKEbu yarışı büyüklük derecelerine göre kazanır.

Kılavuzdaki desen eşleşmeyle ilgili temel bilgileri okuyun .

Üstün performans için endeks

Performansla ilgileniyorsanız, daha büyük tablolar için böyle bir dizin oluşturun:

CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);

Bu tür bir sorguyu büyüklük sırasına göre daha hızlı hale getirir. Yerellere özgü sıralama düzeni için özel hususlar uygulanır. Kılavuzdaki operatör sınıfları hakkında daha fazla bilgi edinin . Standart "C" yerel ayarını kullanıyorsanız (çoğu insan kullanmaz), düz bir dizin (varsayılan operatör sınıfına sahip) yapar.

Böyle bir indeks sadece sol bağlantılı desenler için iyidir (dizgenin başından itibaren).

SIMILAR TOveya temel sol bağlantılı ifadelere sahip normal ifadeler de bu dizini kullanabilir. Ama değil şubesi bulunan (B|D)veya karakter sınıfları [BD](en azından PostgreSQL 9.0 benim testleri olarak).

Trigram eşleşmeleri veya metin araması, özel GIN veya GiST dizinlerini kullanır.

Desen eşleştirme işleçlerine genel bakış

  • LIKE( ~~) basit ve hızlıdır ancak yetenekleri sınırlıdır.
    ILIKE( ~~*) durumda duyarsız değişken.
    pg_trgm, her ikisi için de dizin desteğini genişletir.

  • ~ (düzenli ifade eşleşmesi) güçlüdür ancak daha karmaşıktır ve temel ifadelerden daha fazlası için yavaş olabilir.

  • SIMILAR TOsadece anlamsız . Tuhaf bir melez LIKEve düzenli ifadeler. Asla kullanmam. Aşağıya bakınız.

  • % ek modül tarafından sağlanan "benzerlik" işlecidirpg_trgm. Aşağıya bakınız.

  • @@Metin arama operatörüdür. Aşağıya bakınız.

pg_trgm - trigram eşleştirme

PostgreSQL 9.1'den başlayarak, bir GIN veya GiST endeksi kullanarak herhangi bir / desen (ve basit regexp desenleri ) pg_trgmiçin dizin desteği sağlama uzantısını kolaylaştırabilirsiniz . LIKEILIKE~

Ayrıntılar, örnek ve bağlantılar:

pg_trgmAyrıca bu operatörleri sağlar :

  • % - "benzerlik" operatörü
  • <%(komütatör %>:) - Postgres 9.6 veya sonraki sürümlerde "word_similarity" operatörü
  • <<%(komütatör %>>:) - Postgres 11 veya sonraki sürümlerde "strict_word_similarity" operatörü

Metin arama

Ayrı altyapı ve indeks tipleri ile özel bir desen eşleşmesidir. Sözlükleri kullanır ve kaynak gösterir ve özellikle doğal diller için belgelerdeki kelimeleri bulmak için harika bir araçtır.

Ön ek eşleştirme de desteklenir:

Postgres 9.6’dan beri yapılan kelime öbeğinin yanı sıra :

Kılavuzdaki girişleri ve operatörlere ve fonksiyonlara genel bakışı düşünün .

Bulanık dize eşleşmesi için ek araçlar

Ek modül fuzzystrmatch bazı daha fazla seçenek sunar, ancak performans genellikle yukarıdakilerin hepsinden daha düşüktür.

Özellikle, levenshtein()fonksiyonun çeşitli uygulamaları araçsal olabilir.

Düzenli ifadeler ( ~) neden her zaman daha hızlı SIMILAR TO?

Cevap basit. SIMILAR TOifadeler dahili olarak normal ifadelere yeniden yazılır. Bu nedenle, her SIMILAR TOifade için , en az bir daha hızlı düzenli ifade vardır (bu, ifadeyi yeniden yazma yükünü korur). Kullanmada hiçbir performans kazancı yoktur SIMILAR TO hiç .

Ve LIKE( ~~) ile yapılabilecek basit ifadeler LIKEyine de daha hızlıdır .

SIMILAR TOSQL standardının ilk taslaklarında ortaya çıktığı için yalnızca PostgreSQL'de desteklenir. Hala ondan kurtulmadılar. Ancak bunu kaldırmak ve bunun yerine regexp eşleşmeleri eklemek için planlar var - ya da öyle duydum.

EXPLAIN ANALYZEonu ortaya koyuyor. Sadece herhangi bir masayı kendiniz deneyin!

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

ortaya çıkarır:

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

SIMILAR TOdüzenli bir ifadeyle ( ~) yeniden yazılmıştır .

Bu özel durum için üstün performans

Ancak EXPLAIN ANALYZEdaha fazlasını ortaya koyuyor. Yukarıda belirtilen endeks yerindeyken deneyin:

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

ortaya çıkarır:

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

Dahili olarak, bir yerele farkında değildir indeksi (ile text_pattern_opsveya kullanarak yerel C:) basit sol demirlemiş ifadeleri bu metin desen operatörleri ile yeniden yazıldı ~>=~, ~<=~, ~>~, ~<~. Bu ~, ~~ya da SIMILAR TObenzerleri için geçerlidir.

Aynısı ya da ile olan varchartürlerdeki dizinler için de geçerlidir .varchar_pattern_opscharbpchar_pattern_ops

Yani, orijinal soruya uygulandığında, bu en hızlı yoldur :

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

Tabii ki, bitişik baş harfleri aramanız gerekirse, daha da basitleştirebilirsiniz:

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

Sade kullanımı ~ya da ~~çok küçük kazancı . Performans, en önemli gereksiniminiz değilse, standart operatörlere bağlı kalmalısınız - soruya zaten sahip olduğunuz şeye ulaştınız.


OP isminde bir endekse sahip değil, ancak orijinal sorgularının 2 aralık araştırması ve similarbir tarama içerdiğini biliyor muydunuz ?
Martin Smith,

2
@ MartinSmith: EXPLAIN ANALYZE2 bitmap indeks taramalarını gösteren hızlı bir test . Birden çok bitmap dizin taraması oldukça hızlı bir şekilde birleştirilebilir.
Erwin Brandstetter,

Teşekkürler. Yani değiştirilmesi ile herhangi milaj olacağını ORbirlikte UNION ALLveya değiştirilmesi name LIKE 'B%'ile name >= 'B' AND name <'C'Postgres?
Martin Smith,

1
@ MartinSmith: UNIONolmaz, ancak evet, aralıkları tek bir WHEREmaddede birleştirmek sorguyu hızlandırır. Cevabımı daha ekledi. Tabii ki, yerel ayarlarınızı dikkate almalısınız. Yerel ayarlara duyarlı arama her zaman daha yavaştır.
Erwin Brandstetter

2
@ a_horse_with_no_name: Bilmiyorum bekliyorum. Pg_tgrm'nin GIN endekslerine sahip yeni özellikleri, genel metin araması için bir zevktir. Başında sabitlenmiş bir arama zaten bundan daha hızlı.
Erwin Brandstetter

11

Tabloya bir sütun ekleme hakkında nasıl. Gerçek gereksinimlerinize bağlı olarak:

person_name_start_with_B_or_D (Boolean)

person_name_start_with_char CHAR(1)

person_name_start_with VARCHAR(30)

PostgreSQL, SQL Server'daki baz tablolardaki hesaplanan sütunları desteklemez , ancak yeni sütun tetikleyici ile korunabilir. Açıkçası, bu yeni sütun endekslenecekti.

Alternatif olarak, bir ifadedeki dizin size aynı, daha ucuza verir. Örneğin:

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

İfadeyi koşullarında eşleşen sorgular bu dizini kullanabilir.

Bu şekilde, performans vuruşu veriler oluşturulduğunda veya değiştirildiğinde ortaya çıkar, bu nedenle yalnızca düşük etkinlikli bir ortam için uygun olabilir (yani, okuduğundan daha az yazma).


8

Sen olabilir denemek

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

Postgres'te yukarıdakilerin ya da orijinal ifadenizin yamuk olup olmadığı hakkında hiçbir fikrim yok.

Önerilen dizini oluşturursanız, bunun diğer seçeneklerle nasıl karşılaştırıldığını da duymak istersiniz.

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

1
İşe yaradı ve 1.25 olan 1.19 bir maliyeti var. Teşekkürler !
Lucas Kauffman

2

Geçmişte yaptığım, benzer bir performans sorunuyla karşı karşıya kaldığım, son mektubun ASCII karakterini artırmak ve DAHA FAZLASI yapmaktır. Daha sonra LIKE işlevselliğinin bir alt kümesi için en iyi performansı elde edersiniz. Tabii ki, sadece belirli durumlarda işe yarar, ancak örneğin bir isim aradığınız çok büyük veri kümeleri için performansı abismalden kabul edilebilir hale getirir.


2

Çok eski bir soru, ancak bu soruna başka hızlı bir çözüm buldum:

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

Ascii () işlevi sadece dizenin ilk karakterine bakar.


1
Bu bir dizin kullanıyor mu (name)?
ypercubeᵀᴹ

2

Baş harflerini kontrol etmek için, genellikle "char"(çift tırnak işaretli) için döküm kullanılır . Taşınabilir değil, ama çok hızlı. Dahili olarak, metni basitçe salıverir ve ilk karakteri döndürür ve "char" karşılaştırma işlemleri çok hızlıdır, çünkü tür 1 bayt sabit uzunluktadır:

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

İçin yayın yapmanın unutmayın "char"daha hızlıdır ascii()karşılaştırma 7 eski düz karşı olduğu @ sole021 tarafından slution ama UTF8 uyumlu değildir (veya bu konuda başka bir kodlama), basitçe ilk baytı dönen, yani sadece durumlarda kullanılması gerektiğini -bit ASCII karakterleri.


1

Bu tür vakalarla ilgilenmek için henüz belirtilmeyen iki yöntem vardır:

  1. kısmi (veya bölümlenmiş - tam aralık için el ile oluşturulmuşsa) endeksi - en çok yalnızca bir veri alt kümesi gerektiğinde kullanışlıdır (örneğin, bazı bakımlar sırasında veya bazı raporlar için geçici):

    CREATE INDEX ON spelers WHERE name LIKE 'B%'
  2. tablonun bölümlenmesi (ilk karakteri bölümleme anahtarı olarak kullanmak) - bu teknik özellikle PostgreSQL 10+ (daha az ağrılı bölümleme) ve 11+ (sorgu yürütme sırasında bölüm budaması) dikkate alınmaya değer.

Ayrıca, bir tablodaki veriler sıralanırsa, biri BRIN endeksini kullanmaktan yararlanabilir (ilk karakter üzerinden).


-4

Tek bir karakter karşılaştırması yapmak muhtemelen daha hızlı:

SUBSTR(s.name,1,1)='B' OR SUBSTR(s.name,1,1)='D'

1
Pek sayılmaz. column LIKE 'B%'sütunda substring işlevini kullanmaktan daha verimli olacaktır.
ypercubeᵀᴹ
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.