PostgreSQL รองรับการเปรียบเทียบแบบ“ เน้นเสียง” หรือไม่


99

ใน Microsoft SQL Server เป็นไปได้ที่จะระบุการเปรียบเทียบแบบ "ไม่เน้นเสียง" (สำหรับฐานข้อมูลตารางหรือคอลัมน์) ซึ่งหมายความว่าเป็นไปได้สำหรับแบบสอบถามเช่น

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

เพื่อค้นหาแถวที่มีJoaoชื่อ

ฉันรู้ว่าเป็นไปได้ที่จะตัดสำเนียงจากสตริงใน PostgreSQL โดยใช้ฟังก์ชันการสนับสนุนunaccent_stringแต่ฉันสงสัยว่า PostgreSQL รองรับการเรียง "ไม่เน้นเสียง" เหล่านี้หรือไม่ดังนั้นSELECTข้างต้นจะได้ผล


ดูคำตอบสำหรับการสร้างพจนานุกรม FTS ด้วย unaccent: stackoverflow.com/a/50595181/124486
Evan Carroll

คุณต้องการค้นหาแบบคำนึงถึงตัวพิมพ์เล็กและใหญ่หรือไม่?
Evan Carroll

คำตอบ:


212

ใช้โมดูล unaccentสำหรับสิ่งนั้นซึ่งแตกต่างอย่างสิ้นเชิงกับสิ่งที่คุณกำลังเชื่อมโยง

unaccent คือพจนานุกรมค้นหาข้อความที่ลบสำเนียง (เครื่องหมายกำกับเสียง) ออกจากตัวอักษร

ติดตั้งหนึ่งครั้งต่อฐานข้อมูลด้วย:

CREATE EXTENSION unaccent;

หากคุณได้รับข้อผิดพลาดเช่น:

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

ติดตั้งแพ็คเกจ Contrib บนเซิร์ฟเวอร์ฐานข้อมูลของคุณตามที่ได้รับคำแนะนำในคำตอบที่เกี่ยวข้องนี้:

เหนือสิ่งอื่นใดมันมีฟังก์ชันที่unaccent()คุณสามารถใช้ได้กับตัวอย่างของคุณ (ซึ่งLIKEดูเหมือนว่าไม่จำเป็น)

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

ดัชนี

การใช้ดัชนีสำหรับชนิดของแบบสอบถามที่สร้างดัชนีในการแสดงออก อย่างไรก็ตาม Postgres ยอมรับIMMUTABLEฟังก์ชันสำหรับดัชนีเท่านั้น หากฟังก์ชันสามารถส่งคืนผลลัพธ์ที่แตกต่างกันสำหรับอินพุตเดียวกันดัชนีอาจแตกอย่างเงียบ ๆ

unaccent()STABLEไม่เท่านั้นIMMUTABLE

แต่น่าเสียดายที่unaccent()เป็นเพียงไม่STABLE IMMUTABLEตามหัวข้อนี้เกี่ยวกับ pgsql-bugsนี่เป็นเพราะสาเหตุสามประการ:

  1. ขึ้นอยู่กับลักษณะการทำงานของพจนานุกรม
  2. ไม่มีการเชื่อมต่อแบบใช้สายกับพจนานุกรมนี้
  3. ดังนั้นจึงขึ้นอยู่กับกระแสsearch_pathซึ่งสามารถเปลี่ยนแปลงได้ง่าย

บทเรียนบางอย่างIMMUTABLEเกี่ยวกับการสั่งเว็บเพียงแค่เปลี่ยนความผันผวนของฟังก์ชั่น วิธีการบังคับแบบเดรัจฉานนี้สามารถทำลายได้ภายใต้เงื่อนไขบางประการ

คนอื่น ๆ แนะนำฟังก์ชั่นเสื้อคลุมแบบธรรมดาIMMUTABLE (เหมือนที่ฉันทำเองในอดีต)

มีการถกเถียงกันอย่างต่อเนื่องว่าจะสร้างตัวแปรด้วยพารามิเตอร์สองตัว IMMUTABLEที่ประกาศพจนานุกรมที่ใช้อย่างชัดเจนหรือไม่ อ่านที่นี่หรือที่นี่

อีกหนึ่งทางเลือกที่จะเป็นโมดูลนี้ด้วยการเปลี่ยนรูปunaccent()ฟังก์ชั่นโดย Musicbrainzให้บน Github ยังไม่ได้ทดสอบด้วยตัวเอง. ฉันคิดว่าฉันมีความคิดที่ดีกว่านี้ :

ดีที่สุดสำหรับตอนนี้

วิธีนี้เป็นวิธีที่มีประสิทธิภาพมากขึ้นเป็นโซลูชั่นอื่น ๆ ที่ลอยอยู่รอบ ๆ และปลอดภัยมากขึ้น
สร้างIMMUTABLEฟังก์ชัน SQL wrapper ที่เรียกใช้รูปแบบสองพารามิเตอร์ด้วยฟังก์ชันและพจนานุกรมที่มีคุณสมบัติสคีมาแบบใช้สายยาก

เนื่องจากการซ้อนฟังก์ชันที่ไม่เปลี่ยนรูปจะปิดการใช้งานฟังก์ชันอินไลน์อิงตามสำเนาของฟังก์ชัน C (ปลอม) ที่ประกาศIMMUTABLEด้วยเช่นกัน ใช้เพียงวัตถุประสงค์เพื่อนำมาใช้ในฟังก์ชั่นเสื้อคลุม SQL ไม่ได้มีไว้สำหรับใช้เอง

จำเป็นต้องมีความซับซ้อนเนื่องจากไม่มีวิธีการต่อสายพจนานุกรมในการประกาศฟังก์ชัน C (จำเป็นต้องแฮ็กโค้ด C เอง) ฟังก์ชัน SQL wrapper จะทำเช่นนั้นและอนุญาตให้ทั้งฟังก์ชันอินไลน์และดัชนีนิพจน์

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$;

ดร็อปPARALLEL SAFEจากทั้งสองฟังก์ชันสำหรับ Postgres 9.5 ขึ้นไป

publicเป็นสคีมาที่คุณติดตั้งส่วนขยาย ( publicเป็นค่าเริ่มต้น)

การประกาศประเภทอย่างชัดเจน ( regdictionary) ป้องกันการโจมตีสมมุติด้วยฟังก์ชันที่มีรูปแบบมากเกินไปโดยผู้ใช้ที่เป็นอันตราย

ก่อนหน้านี้ฉันสนับสนุนฟังก์ชัน wrapper ตามSTABLEฟังก์ชันที่unaccent()มาพร้อมกับโมดูล unaccent ที่ปิดการใช้งานอินไลน์ฟังก์ชั่น เวอร์ชันนี้ทำงานได้เร็วกว่าฟังก์ชัน Wrapper แบบธรรมดาถึง 10 เท่าที่ฉันมีก่อนหน้านี้
และนั่นเร็วกว่าเวอร์ชันแรกถึงสองเท่าซึ่งเพิ่มลงSET search_path = public, pg_tempในฟังก์ชันแล้ว - จนกระทั่งฉันค้นพบว่าพจนานุกรมนั้นสามารถเข้าเกณฑ์สคีมาได้เช่นกัน ยังคง (Postgres 12) ไม่ชัดเจนเกินไปจากเอกสาร

หากคุณไม่มีสิทธิ์ที่จำเป็นในการสร้างฟังก์ชัน C คุณจะกลับสู่การใช้งานที่ดีที่สุดอันดับสอง: IMMUTABLEฟังก์ชัน wrapper รอบ ๆSTABLE unaccent()ฟังก์ชันที่จัดเตรียมโดยโมดูล:

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;

ในที่สุดดัชนีนิพจน์เพื่อให้การสืบค้นรวดเร็ว :

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

อย่าลืมสร้างดัชนีที่เกี่ยวข้องกับฟังก์ชันนี้ใหม่หลังจากการเปลี่ยนแปลงฟังก์ชันหรือพจนานุกรมเช่นการอัปเกรดรุ่นหลักแบบแทนที่ซึ่งจะไม่สร้างดัชนีขึ้นมาใหม่ รุ่นใหญ่ล่าสุดทั้งหมดมีการอัปเดตสำหรับunaccentโมดูล

ปรับการสืบค้นให้ตรงกับดัชนี (เพื่อให้ผู้วางแผนการสืบค้นใช้):

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

คุณไม่ต้องการฟังก์ชันในนิพจน์ที่ถูกต้อง นอกจากนี้คุณยังสามารถจัดหาสตริงที่ไม่เน้นเสียง'Joao'ได้โดยตรง

ฟังก์ชั่นได้เร็วขึ้นไม่ได้แปลไปได้เร็วขึ้นมากแบบสอบถามโดยใช้ดัชนีการแสดงออก ซึ่งทำงานบนค่าที่คำนวณล่วงหน้าและรวดเร็วมากอยู่แล้ว แต่การบำรุงรักษาดัชนีและแบบสอบถามไม่ใช้ประโยชน์ของดัชนี

การรักษาความปลอดภัยสำหรับโปรแกรมไคลเอ็นต์ได้รับการรัดกุมด้วย Postgres 10.3 / 9.6.8 เป็นต้นคุณจำเป็นต้องกำหนดฟังก์ชันที่มีคุณสมบัติสคีมาและชื่อพจนานุกรมตามที่แสดงเมื่อใช้ในดัชนีใด ๆ ดู:

ลิเก

ในอักษรย่อPostgres 9.5 หรือเก่ากว่าเช่น 'Œ' หรือ 'ß' จะต้องขยายด้วยตนเอง (ถ้าคุณต้องการ) เนื่องจากunaccent()แทนที่ตัวอักษรตัวเดียวเสมอ:

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

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

คุณจะหลงรักการอัปเดตนี้ถึงไม่มีใครสนใจใน Postgres 9.6 :

ขยายไฟล์contrib/unaccentมาตรฐานunaccent.rulesเพื่อจัดการตัวกำกับเสียงทั้งหมดที่ Unicode รู้จักและขยายตัวอักษรอย่างถูกต้อง (Thomas Munro, Léonard Benedetti)

ฉันเน้นตัวหนา ตอนนี้เราได้รับ:

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

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

การจับคู่รูปแบบ

สำหรับLIKEหรือILIKEกับรูปแบบที่กำหนดเองให้รวมเข้ากับโมดูลpg_trgmใน PostgreSQL 9.1 หรือใหม่กว่า สร้าง Trigram GIN (โดยทั่วไปแล้วจะดีกว่า) หรือดัชนีนิพจน์ GIST ตัวอย่างสำหรับ GIN:

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

สามารถใช้สำหรับการค้นหาเช่น:

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

ดัชนี GIN และ GIST มีราคาแพงกว่าในการดูแลรักษามากกว่า btree ธรรมดา:

มีวิธีแก้ปัญหาที่ง่ายกว่าสำหรับรูปแบบที่ยึดด้านซ้ายเท่านั้น ข้อมูลเพิ่มเติมเกี่ยวกับการจับคู่รูปแบบและประสิทธิภาพ:

pg_trgmนอกจากนี้ยังมีประโยชน์ผู้ประกอบการสำหรับ "คล้ายคลึงกัน" ( %) และ "ระยะทาง" (<-> )

ดัชนี Trigram ยังรองรับนิพจน์ทั่วไปอย่างง่ายด้วย~et al และรูปแบบที่ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่จับคู่กับILIKE:


ในโซลูชันของคุณมีการใช้ดัชนีหรือฉันต้องสร้างดัชนีunaccent(name)?
Daniel Serodio

1
@ e3matheus: รู้สึกผิดที่ไม่ได้ทดสอบวิธีแก้ปัญหาก่อนหน้านี้ที่ฉันให้ไว้ฉันได้ตรวจสอบและอัปเดตคำตอบของฉันด้วยโซลูชัน (IMHO) ใหม่และดีกว่าสำหรับปัญหาที่เกิดขึ้น
Erwin Brandstetter

5
คำตอบของคุณดีพอ ๆ กับเอกสารของ Postgres: ปรากฎการณ์!
electrotype

1
ฉันสงสัยว่าตอนนี้ความรู้สึกไม่ไวต่อสำเนียงเป็นไปได้หรือไม่ด้วยการเรียง ICU
a_horse_with_no_name

1
@a_horse_with_no_name: ฉันยังไม่มีเวลาทดสอบ แต่นั่นเป็นกรณีการใช้งานที่ตั้งใจไว้
Erwin Brandstetter

6

ไม่ PostgreSQL ไม่สนับสนุนการเรียงลำดับในแง่นั้น

PostgreSQL ไม่สนับสนุนการจัดเรียงแบบนั้น (ไม่เน้นเสียงหรือไม่) เนื่องจากไม่มีการเปรียบเทียบใดที่สามารถคืนค่าเท่ากันได้เว้นแต่สิ่งต่างๆจะเท่ากับไบนารี เนื่องจากภายในจะมีความซับซ้อนมากมายสำหรับสิ่งต่างๆเช่นดัชนีแฮช ด้วยเหตุนี้การจัดเรียงในแง่ที่เข้มงวดที่สุดจึงส่งผลต่อการสั่งซื้อเท่านั้นไม่ใช่ความเท่าเทียมกัน

วิธีแก้ปัญหา

พจนานุกรมการค้นหาข้อความแบบเต็มที่ Unaccents lexemes

สำหรับ FTS คุณสามารถกำหนดพจนานุกรมของคุณเองโดยใช้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;

ซึ่งคุณสามารถจัดทำดัชนีด้วยดัชนีการทำงาน

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

ตอนนี้คุณสามารถสอบถามได้อย่างง่ายดาย

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

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

ดูสิ่งนี้ด้วย

Unaccent ด้วยตัวมันเอง

unaccentโมดูลยังสามารถนำมาใช้ด้วยตัวเองโดยไม่ต้อง FTS-บูรณาการสำหรับการตรวจสอบว่าคำตอบของเออร์วิน


โปรดทราบว่าย่อหน้าเริ่มต้นที่นี่ไม่เป็นความจริงอย่างเคร่งครัดอีกต่อไปเช่นเดียวกับ Postgres 12 ซึ่งนำเสนอการเปรียบเทียบแบบไม่กำหนด อย่างไรก็ตามตัวดำเนินการจับคู่รูปแบบยังไม่รองรับ
Inkling

2

ฉันค่อนข้างมั่นใจว่า PostgreSQL ต้องอาศัยระบบปฏิบัติการพื้นฐานสำหรับการเปรียบเทียบ มันไม่สนับสนุนการสร้างเรียงใหม่และการปรับแต่งเรียง ฉันไม่แน่ใจว่าอาจจะเหมาะกับคุณมากแค่ไหน (อาจจะค่อนข้างมาก)


1
การสนับสนุนการจัดเรียงใหม่โดยพื้นฐานแล้วจะ จำกัด อยู่ที่ wrapper และ aliases สำหรับโลแคลระบบปฏิบัติการ มันธรรมดามาก ไม่มีการรองรับฟังก์ชันตัวกรองเครื่องมือเปรียบเทียบแบบกำหนดเองหรือสิ่งใด ๆ ที่คุณต้องการสำหรับการเปรียบเทียบแบบกำหนดเองที่แท้จริง
Craig Ringer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.