PostgreSQL có hỗ trợ đối chiếu “không nhạy cảm” không?


98

Trong Microsoft SQL Server, có thể chỉ định đối chiếu "không phân biệt dấu" (đối với cơ sở dữ liệu, bảng hoặc cột), có nghĩa là có thể có một truy vấn như

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

để tìm một hàng có Joaotên.

Tôi biết rằng nó có thể tước điểm nhấn từ chuỗi trong PostgreSQL sử dụng unaccent_string chức năng contrib, nhưng tôi đang tự hỏi nếu PostgreSQL hỗ trợ các "giọng vô cảm" collations nên SELECTở trên sẽ làm việc.


Xem câu trả lời này để tạo từ điển FTS với unaccent: stackoverflow.com/a/50595181/124486
Evan Carroll

Bạn muốn tìm kiếm phân biệt chữ hoa chữ thường hay không phân biệt chữ hoa chữ thường?
Evan Carroll

Câu trả lời:


204

Sử dụng mô-đun không phù hợp cho điều đó - hoàn toàn khác với mô-đun bạn đang liên kết.

unaccent là một từ điển tìm kiếm văn bản loại bỏ dấu (dấu phụ) khỏi các từ vựng.

Cài đặt một lần cho mỗi cơ sở dữ liệu với:

CREATE EXTENSION unaccent;

Nếu bạn gặp lỗi như:

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

Cài đặt gói đóng góp trên máy chủ cơ sở dữ liệu của bạn như được hướng dẫn trong câu trả lời liên quan này:

Trong số những thứ khác, nó cung cấp chức năng unaccent()bạn có thể sử dụng với ví dụ của mình (nếu LIKEkhông cần thiết).

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

Mục lục

Để sử dụng chỉ mục cho loại truy vấn đó, hãy tạo chỉ mục trên biểu thức . Tuy nhiên , Postgres chỉ chấp nhận các IMMUTABLEhàm cho các chỉ mục. Nếu một hàm có thể trả về một kết quả khác cho cùng một đầu vào, thì chỉ mục có thể bị ngắt một cách âm thầm.

unaccent()chỉ STABLEkhôngIMMUTABLE

Thật không may, unaccent()là duy nhất STABLE, không phải IMMUTABLE. Theo chủ đề này trên pgsql-bug , điều này là do ba lý do:

  1. Nó phụ thuộc vào hành vi của một từ điển.
  2. Không có kết nối có dây với từ điển này.
  3. Do đó, nó cũng phụ thuộc vào dòng điện search_path, có thể thay đổi dễ dàng.

Một số hướng dẫn trên web hướng dẫn chỉ thay đổi tính biến động của chức năng IMMUTABLE. Phương pháp vũ phu này có thể phá vỡ trong một số điều kiện nhất định.

Những người khác đề xuất một chức năng trình bao bọc đơn giảnIMMUTABLE (giống như tôi đã tự làm trong quá khứ).

Có một cuộc tranh luận đang diễn ra liệu có nên tạo biến thể có hai tham số IMMUTABLE khai báo từ điển đã sử dụng một cách rõ ràng hay không. Đọc ở đây hoặc ở đây .

Một giải pháp thay thế khác sẽ là mô-đun này có chức năng IMMUTABLE unaccent()của Musicbrainz , được cung cấp trên Github. Chưa tự mình kiểm tra. Tôi nghĩ tôi đã nghĩ ra một ý tưởng hay hơn :

Tốt nhất cho bây giờ

Cách tiếp cận này hiệu quả hơn khi các giải pháp khác trôi nổi và an toàn hơn .
Tạo một IMMUTABLEhàm trình bao bọc SQL thực thi biểu mẫu hai tham số với từ điển và hàm đủ điều kiện giản đồ có dây.

Vì việc lồng một hàm không thay đổi sẽ vô hiệu hóa nội tuyến của hàm, hãy căn cứ vào bản sao của hàm C, được khai báo (giả mạo) IMMUTABLE. Mục đích duy nhất của nó là được sử dụng trong trình bao bọc hàm SQL. Không có nghĩa là để được sử dụng riêng của mình.

Sự tinh tế là cần thiết vì không có cách nào làm khó từ điển trong phần khai báo hàm C. (Sẽ yêu cầu tự hack mã C.) Hàm trình bao bọc trong SQL thực hiện điều đó và cho phép cả chỉ mục nội tuyến chỉ mục biểu thức.

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

Bỏ PARALLEL SAFEqua cả hai chức năng cho Postgres 9.5 trở lên.

publiclà lược đồ nơi bạn đã cài đặt tiện ích mở rộng ( publiclà mặc định).

Khai báo kiểu rõ ràng ( regdictionary) bảo vệ chống lại các cuộc tấn công giả định với các biến thể quá tải của hàm bởi người dùng độc hại.

Trước đây, tôi đã ủng hộ một chức năng bao bọc dựa trên STABLEchức năng unaccent()được vận chuyển cùng với mô-đun không lệch tâm. Đã vô hiệu hóa chức năng nội tuyến . Phiên bản này thực thi nhanh hơn mười lần so với chức năng trình bao bọc đơn giản mà tôi đã có ở đây trước đó.
Và tốc độ đó đã nhanh gấp đôi so với phiên bản đầu tiên được thêm vào SET search_path = public, pg_temphàm - cho đến khi tôi phát hiện ra rằng từ điển cũng có thể đủ tiêu chuẩn giản đồ. Tuy nhiên (Postgres 12) không quá rõ ràng từ tài liệu.

Nếu bạn thiếu các đặc quyền cần thiết để tạo các hàm C, bạn quay lại cách triển khai tốt thứ hai: Một IMMUTABLEtrình bao bọc hàm xung quanh STABLE unaccent()hàm được cung cấp bởi mô-đun:

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;

Cuối cùng, chỉ mục biểu thức để thực hiện các truy vấn nhanh chóng :

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

Hãy nhớ tạo lại các chỉ mục liên quan đến chức năng này sau bất kỳ thay đổi nào đối với chức năng hoặc từ điển, chẳng hạn như bản nâng cấp phát hành chính tại chỗ sẽ không tạo lại các chỉ mục. Các bản phát hành chính gần đây đều có cập nhật cho unaccentmô-đun.

Điều chỉnh các truy vấn để phù hợp với chỉ mục (vì vậy người lập kế hoạch truy vấn sẽ sử dụng nó):

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

Bạn không cần hàm trong biểu thức đúng. Ở đó, bạn cũng có thể cung cấp các chuỗi không có phép như 'Joao'trực tiếp.

Hàm nhanh hơn không dịch sang các truy vấn nhanh hơn nhiều bằng cách sử dụng chỉ mục biểu thức . Điều đó hoạt động dựa trên các giá trị được tính toán trước và đã rất nhanh. Nhưng bảo trì chỉ mục và các truy vấn không sử dụng chỉ mục có lợi.

Bảo mật cho các chương trình máy khách đã được thắt chặt với Postgres 10.3 / 9.6.8, v.v. Bạn cần xác định chức năng đủ điều kiện giản đồ và tên từ điển như được minh họa khi được sử dụng trong bất kỳ chỉ mục nào. Xem:

Dây chằng

Trong Postgres 9,5 trở lên ligature như 'Œ' hoặc 'ß' phải được mở rộng bằng tay (nếu bạn cần đó), vì unaccent()luôn luôn thay thế một đơn thư:

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

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

Bạn sẽ thích bản cập nhật này cho người không công tâm trong Postgres 9.6 :

Mở rộng tệp contrib/unaccenttiêu chuẩn của unaccent.rulesđể xử lý tất cả các dấu phụ được biết đến với Unicode và mở rộng các chữ ghép một cách chính xác (Thomas Munro, Léonard Benedetti)

Tôi nhấn mạnh đậm. Bây giờ chúng tôi nhận được:

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

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

Khớp mẫu

Đối với LIKEhoặc ILIKEvới các mẫu tùy ý, hãy kết hợp mô-đun này với mô-đun pg_trgmtrong PostgreSQL 9.1 trở lên. Tạo chỉ mục biểu thức GIN (thường thích hợp hơn) hoặc GIST. Ví dụ cho GIN:

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

Có thể được sử dụng cho các truy vấn như:

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

Chỉ số GIN và GIST đắt hơn để duy trì so với btree đơn giản:

Có nhiều giải pháp đơn giản hơn cho các mẫu được neo bên trái. Thông tin thêm về đối sánh mẫu và hiệu suất:

pg_trgmcũng cung cấp các toán tử%<-> hữu ích cho "tương tự" ( ) và "khoảng cách" ( ) .

Chỉ mục Trigram cũng hỗ trợ các biểu thức chính quy đơn giản với ~et al. và đối sánh mẫu không phân biệt chữ hoa chữ thường với ILIKE:


Trong giải pháp của bạn, các chỉ mục có được sử dụng không hay tôi cần tạo chỉ mục trên unaccent(name)?
Daniel Serodio

@ErwinBrandstetter Trong psql 9.1.4, tôi nhận được "các hàm trong biểu thức chỉ mục phải được đánh dấu là NGAY LẬP TỨC", bởi vì hàm không sai lệch là ỔN ĐỊNH, thay vì KHÔNG CÓ THỂ. Bạn đề xuất món gì?
e3matheus

1
@ e3matheus: Cảm thấy có lỗi vì đã không thử nghiệm giải pháp trước đó mà tôi đã cung cấp, tôi đã điều tra và cập nhật câu trả lời của mình bằng một giải pháp mới và tốt hơn (IMHO) cho vấn đề so với những gì đang trôi nổi cho đến nay.
Erwin Brandstetter

Đối chiếu không phải là utf8_general_cicâu trả lời cho loại vấn đề này?
Med

5
Câu trả lời của bạn cũng tốt như tài liệu của Postgres: thật phi thường!
electrotype

6

Không, PostgreSQL không hỗ trợ đối chiếu theo nghĩa đó

PostgreSQL không hỗ trợ các đối chiếu như vậy (không phân biệt trọng âm hoặc không) bởi vì không có so sánh nào có thể trả về bằng nhau trừ khi mọi thứ là nhị phân bằng. Điều này là do bên trong nó sẽ tạo ra rất nhiều phức tạp cho những thứ như chỉ mục băm. Vì lý do này, các đối chiếu theo nghĩa chặt chẽ nhất của chúng chỉ ảnh hưởng đến thứ tự chứ không phải sự bình đẳng.

Cách giải quyết

Từ điển tìm kiếm toàn văn bản mà Người không hiểu.

Đối với FTS, bạn có thể xác định từ điển của riêng mình bằng cách sử dụng 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;

Sau đó, bạn có thể lập chỉ mục với chỉ mục chức năng,

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

Bây giờ bạn có thể truy vấn nó rất đơn giản

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

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

Xem thêm

Không phải của chính nó.

Các unaccentmô-đun cũng có thể được sử dụng bởi chính nó mà không FTS-hội nhập, cho rằng kiểm tra ra câu trả lời Erwin của


2

Tôi khá chắc chắn rằng PostgreSQL dựa vào hệ điều hành cơ bản để đối chiếu. Nó không hỗ trợ tạo collations mới , và tùy biến collations . Tuy nhiên, tôi không chắc có bao nhiêu công việc có thể dành cho bạn. (Có thể là khá nhiều.)


1
Hỗ trợ đối chiếu mới về cơ bản hiện chỉ giới hạn ở các trình bao bọc và bí danh cho ngôn ngữ hệ điều hành. Nó rất cơ bản. Không có hỗ trợ cho các chức năng bộ lọc, bộ so sánh tùy chỉnh hoặc bất kỳ thứ gì bạn cần cho các đối chiếu tùy chỉnh thực sự.
Craig Ringer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.