Apakah PostgreSQL mendukung pemeriksaan "tidak peka aksen"?

98

Di Microsoft SQL Server, dimungkinkan untuk menentukan pemeriksaan "tidak peka aksen" (untuk database, tabel, atau kolom), yang berarti mungkin untuk kueri seperti

SELECT * FROM users WHERE name LIKE 'João'

untuk menemukan baris dengan Joaonama.

Saya tahu bahwa mungkin saja untuk menghapus aksen dari string di PostgreSQL menggunakan fungsi kontrib unaccent_string, tetapi saya bertanya-tanya apakah PostgreSQL mendukung susunan "tidak sensitif aksen" ini sehingga cara di SELECTatas akan berfungsi.

Daniel Serodio
sumber
Lihat jawaban ini untuk membuat kamus FTS dengan unaccent: stackoverflow.com/a/50595181/124486
Evan Carroll
Apakah Anda ingin penelusuran case-sensitive atau case insensitive?
Evan Carroll

Jawaban:

204

Gunakan modul unaccent untuk itu - yang sama sekali berbeda dari apa yang Anda tautkan.

unaccent adalah kamus pencarian teks yang menghilangkan aksen (tanda diakritik) dari leksem.

Instal sekali per database dengan:

CREATE EXTENSION unaccent;

Jika Anda mendapatkan kesalahan seperti:

ERROR: could not open extension control file
"/usr/share/postgresql/<version>/extension/unaccent.control": No such file or directory

Instal paket contrib di server database Anda seperti yang diperintahkan dalam jawaban terkait ini:

Antara lain, ini menyediakan fungsi unaccent()yang dapat Anda gunakan dengan contoh Anda (jika LIKEtampaknya tidak diperlukan).

SELECT *
FROM   users
WHERE  unaccent(name) = unaccent('João');

Indeks

Untuk menggunakan indeks untuk jenis kueri tersebut, buat indeks pada ekspresi . Namun , Postgres hanya menerima IMMUTABLEfungsi untuk indeks. Jika suatu fungsi dapat mengembalikan hasil yang berbeda untuk input yang sama, indeks dapat rusak secara diam-diam.

unaccent()hanya STABLEtidakIMMUTABLE

Sayangnya, unaccent()hanya STABLEtidak IMMUTABLE. Menurut utas tentang pgsql-bugs ini, ini karena tiga alasan:

  1. Itu tergantung pada perilaku kamus.
  2. Tidak ada koneksi terprogram ke kamus ini.
  3. Oleh karena itu, ini juga tergantung pada arus search_path, yang dapat berubah dengan mudah.

Beberapa tutorial di web menginstruksikan untuk mengubah volatilitas fungsi menjadi IMMUTABLE. Metode brute force ini bisa rusak dalam kondisi tertentu.

Yang lain menyarankan fungsi pembungkus sederhanaIMMUTABLE (seperti yang saya lakukan di masa lalu).

Ada perdebatan yang sedang berlangsung apakah akan membuat varian dengan dua parameter IMMUTABLE yang mendeklarasikan kamus yang digunakan secara eksplisit. Baca di sini atau di sini .

Alternatif lain adalah modul ini dengan fungsi IMMUTABLE unaccent()oleh Musicbrainz , yang disediakan di Github. Belum mengujinya sendiri. Saya pikir saya telah mendapatkan ide yang lebih baik :

Terbaik untuk saat ini

Pendekatan ini lebih efisien karena solusi lain yang beredar, dan lebih aman .
Buat IMMUTABLEfungsi pembungkus SQL yang menjalankan formulir dua parameter dengan fungsi dan kamus yang memenuhi syarat skema berkabel.

Karena menumpuk fungsi yang tidak dapat diubah akan menonaktifkan fungsi sebaris, mendasarkannya pada salinan fungsi-C, (palsu) juga dideklarasikan IMMUTABLE. Satu - satunya tujuan adalah digunakan dalam pembungkus fungsi SQL. Tidak dimaksudkan untuk digunakan sendiri.

Kecanggihan diperlukan karena tidak ada cara untuk menghubungkan kamus secara keras dalam deklarasi fungsi C. (Akan perlu meretas kode C itu sendiri.) Fungsi SQL wrapper melakukan itu dan memungkinkan kedua fungsi sebaris dan indeks ekspresi.

CREATE OR REPLACE FUNCTION public.immutable_unaccent(regdictionary, text)
  RETURNS text LANGUAGE c IMMUTABLE PARALLEL SAFE STRICT AS
'$libdir/unaccent', 'unaccent_dict';

CREATE OR REPLACE FUNCTION public.f_unaccent(text)
  RETURNS text LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT AS
$func$
SELECT public.immutable_unaccent(regdictionary 'public.unaccent', $1)
$func$;

Turun PARALLEL SAFEdari kedua fungsi untuk Postgres 9.5 atau yang lebih lama.

publicmenjadi skema tempat Anda memasang ekstensi ( publicadalah default).

Deklarasi tipe eksplisit ( regdictionary) melindungi dari serangan hipotetis dengan varian fungsi yang kelebihan beban oleh pengguna jahat.

Sebelumnya, saya menganjurkan fungsi pembungkus berdasarkan STABLEfungsi yang unaccent()dikirimkan dengan modul unaccent. Fungsi yang dinonaktifkan itu sebaris . Versi ini dijalankan sepuluh kali lebih cepat daripada fungsi pembungkus sederhana yang saya miliki di sini sebelumnya.
Dan itu sudah dua kali lebih cepat dari versi pertama yang ditambahkan SET search_path = public, pg_tempke fungsi - sampai saya menemukan bahwa kamus juga bisa memenuhi syarat skema. Masih (Postgres 12) tidak terlalu terlihat dari dokumentasinya.

Jika Anda tidak memiliki hak istimewa yang diperlukan untuk membuat fungsi C, Anda kembali ke implementasi terbaik kedua: Pembungkus IMMUTABLEfungsi di sekitar STABLE unaccent()fungsi yang disediakan oleh modul:

CREATE OR REPLACE FUNCTION public.f_unaccent(text)
  RETURNS text AS
$func$
SELECT public.unaccent('public.unaccent', $1)  -- schema-qualify function and dictionary
$func$  LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT;

Akhirnya, indeks ekspresi membuat kueri cepat :

CREATE INDEX users_unaccent_name_idx ON users(public.f_unaccent(name));

Ingatlah untuk membuat ulang indeks yang melibatkan fungsi ini setelah perubahan apa pun ke fungsi atau kamus, seperti pemutakhiran rilis utama di tempat yang tidak akan membuat ulang indeks. Rilis utama terbaru semuanya memiliki pembaruan untuk unaccentmodul.

Sesuaikan kueri agar cocok dengan indeks (sehingga perencana kueri akan menggunakannya):

SELECT * FROM users
WHERE  f_unaccent(name) = f_unaccent('João');

Anda tidak membutuhkan fungsi dalam ekspresi yang benar. Di sana Anda juga dapat memberikan string tanpa aksen seperti 'Joao'secara langsung.

Fungsi yang lebih cepat tidak menerjemahkan ke kueri yang jauh lebih cepat menggunakan indeks ekspresi . Itu beroperasi pada nilai yang telah dihitung sebelumnya dan sudah sangat cepat. Tetapi pemeliharaan indeks dan kueri tidak menggunakan manfaat indeks.

Keamanan untuk program klien telah diperketat dengan Postgres 10.3 / 9.6.8 dll. Anda perlu fungsi yang memenuhi syarat skema dan nama kamus seperti yang ditunjukkan saat digunakan dalam indeks apa pun. Lihat:

Ligatur

Di Postgres 9.5 atau ligatur yang lebih lama seperti 'Œ' atau 'ß' harus diperluas secara manual (jika Anda membutuhkannya), karena unaccent()selalu mengganti satu huruf:

SELECT unaccent('Œ Æ œ æ ß');

unaccent
----------
E A e a S

Anda akan menyukai pembaruan ini untuk unaccent di Postgres 9.6 :

Perluas file contrib/unaccentstandar unaccent.rulesuntuk menangani semua diakritik yang dikenal dengan Unicode, dan perluas pengikat dengan benar (Thomas Munro, Léonard Benedetti)

Penekanan saya yang berani. Sekarang kita mendapatkan:

SELECT unaccent('Œ Æ œ æ ß');

unaccent
----------
OE AE oe ae ss

Pencocokan pola

Untuk LIKEatau ILIKEdengan pola arbitrer, gabungkan ini dengan modul pg_trgmdi PostgreSQL 9.1 atau yang lebih baru. Buat trigram GIN (biasanya lebih disukai) atau indeks ekspresi GIST. Contoh untuk GIN:

CREATE INDEX users_unaccent_name_trgm_idx ON users
USING gin (f_unaccent(name) gin_trgm_ops);

Dapat digunakan untuk pertanyaan seperti:

SELECT * FROM users
WHERE  f_unaccent(name) LIKE ('%' || f_unaccent('João') || '%');

Indeks GIN dan GIST lebih mahal pemeliharaannya daripada btree biasa:

Ada solusi yang lebih sederhana untuk pola jangkar kiri saja. Lebih lanjut tentang pencocokan pola dan kinerja:

pg_trgmjuga menyediakan operator yang berguna untuk "kesamaan" (% ) dan "jarak" ( <->) .

Indeks trigram juga mendukung ekspresi reguler sederhana dengan ~et al. dan pola tidak peka huruf besar / kecil yang cocok dengan ILIKE:

Erwin Brandstetter
sumber
Dalam solusi Anda, apakah indeks digunakan, atau apakah saya perlu membuat indeks unaccent(name)?
Daniel Serodio
@ErwinBrandstetter Dalam psql 9.1.4, saya mendapatkan "fungsi dalam ekspresi indeks harus ditandai IMMUTABLE", karena fungsi unaccent adalah STABIL, bukan INMUTABLE. Apa yang kamu sarankan?
e3matheus
1
@ e3matheus: Merasa bersalah karena tidak menguji solusi sebelumnya yang saya berikan, saya menyelidiki dan memperbarui jawaban saya dengan solusi baru dan lebih baik (IMHO) untuk masalah tersebut daripada yang beredar sejauh ini.
Erwin Brandstetter
Bukankah penyusunannya utf8_general_cimerupakan jawaban untuk masalah seperti ini?
Med
5
Jawaban Anda sebagus dokumentasi Postgres: fenomenal!
electrotype
6

Tidak, PostgreSQL tidak mendukung pemeriksaan dalam hal itu

PostgreSQL tidak mendukung pemeriksaan seperti itu (tidak sensitif aksen atau tidak) karena tidak ada perbandingan yang dapat menghasilkan nilai yang sama kecuali jika semuanya sama-biner. Ini karena secara internal akan memperkenalkan banyak kerumitan untuk hal-hal seperti indeks hash. Karena alasan ini, pemeriksaan dalam arti yang paling ketat hanya memengaruhi pengurutan , bukan kesetaraan.

Solusi

Kamus Pencarian-Teks Penuh yang Menghapus Aksen leksem.

Untuk FTS, Anda dapat menentukan kamus Anda sendiri menggunakan unaccent,

CREATE EXTENSION unaccent;

CREATE TEXT SEARCH CONFIGURATION mydict ( COPY = simple );
ALTER TEXT SEARCH CONFIGURATION mydict
  ALTER MAPPING FOR hword, hword_part, word
  WITH unaccent, simple;

Yang kemudian dapat Anda indeks dengan indeks fungsional,

-- Just some sample data...
CREATE TABLE myTable ( myCol )
  AS VALUES ('fóó bar baz'),('qux quz');

-- No index required, but feel free to create one
CREATE INDEX ON myTable
  USING GIST (to_tsvector('mydict', myCol));

Anda sekarang dapat menanyakannya dengan sangat sederhana

SELECT *
FROM myTable
WHERE to_tsvector('mydict', myCol) @@ 'foo & bar'

    mycol    
-------------
 fóó bar baz
(1 row)

Lihat juga

Unaccent dengan sendirinya.

The unaccentmodul juga dapat digunakan dengan sendirinya tanpa FTS-integrasi, untuk itu memeriksa jawaban Erwin

Evan Carroll
sumber
2

Saya cukup yakin PostgreSQL bergantung pada sistem operasi yang mendasari untuk pemeriksaan. Ini tidak mendukung menciptakan collations baru , dan menyesuaikan collations . Saya tidak yakin berapa banyak pekerjaan yang mungkin untuk Anda. (Bisa jadi cukup banyak.)

Mike Sherrill 'Cat Recall'
sumber
1
Dukungan pemeriksaan baru saat ini pada dasarnya terbatas pada pembungkus dan alias untuk lokal sistem operasi. Itu sangat mendasar. Tidak ada dukungan untuk fungsi filter, pembanding kustom, atau apa pun yang Anda perlukan untuk penyusunan kustom yang sebenarnya.
Craig Ringer