Поддерживает ли PostgreSQL "нечувствительные к акценту" сортировки?

В Microsoft SQL Server можно указать "нечувствительную к акценту" сортировку (для базы данных, таблицы или столбца), что означает, что это возможно для запроса типа

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

чтобы найти строку с именем Joao.

Я знаю, что можно стянуть акценты из строк в PostgreSQL с помощью функции unaccent_string contrib, но мне интересно, поддерживает ли PostgreSQL эти "нечувствительные к акценту" сортировки, поэтому выше SELECT.

Ответ 1

Для этого используйте модуль unacent, который полностью отличается от того, на что вы ссылаетесь.

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');
Индекс

Index

Чтобы использовать индекс для этого типа запроса, создайте индекс для выражения. Однако, Postgres принимает только функции IMMUTABLE для индексов. Если функция может вернуть другой результат для одного и того же ввода, индекс может молча прерваться.

unaccent() только STABLE не IMMUTABLE

К сожалению, unaccent() является только STABLE, а не IMMUTABLE. Согласно этой теме о pgsql-ошибках, это связано с тремя причинами:

  1. Это зависит от поведения словаря.
  2. К этому словарю нет проводного соединения.
  3. Поэтому он также зависит от тока search_path, который может легко меняться.

Некоторые учебные пособия в Интернете предписывают просто изменить волатильность функции на IMMUTABLE. Этот метод грубой силы может сломаться при определенных условиях.

Другие предлагают простую IMMUTABLE функцию-обертку (как я делал это раньше).

В настоящее время ведутся споры о создании варианта с двумя параметрами IMMUTABLE, который явно объявляет используемый словарь. Читайте здесь или здесь.

Другой альтернативой может быть этот модуль с функцией IMMUTABLE unaccent() от Musicbrainz, предоставляемой на Github. Сам не проверял. Я думаю, что придумал лучшую идею:

Лучше всего сейчас

Этот подход более эффективен, чем другие решения, и более безопасен.
Создайте функцию-оболочку SQL IMMUTABLE, выполнив двухпараметрическую форму со встроенной функцией, определяемой схемой, и словарем.

Поскольку вложение неизменяемой функции отключило бы встраивание функции, основывайте ее на копии C-функции, (поддельной), объявленной также IMMUTABLE. Его единственное назначение - использовать в оболочке функции SQL. Не предназначен для самостоятельного использования.

Требуется сложность, поскольку нет способа жестко связать словарь в объявлении функции C. (Требуется взломать сам код на языке C). Функция-оболочка SQL делает это и допускает как встраивание функций, так и индексы выражений.

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) защищает от гипотетических атак с помощью перегруженных вариантов функции со стороны злоумышленников.

Ранее я поддерживал функцию обертки, основанную на функции STABLE unaccent(), поставляемой с модулем unaccent. Это отключило функцию встраивания. Эта версия выполняется в десять раз быстрее, чем простая функция-обертка, которая была у меня здесь ранее.
И это было уже в два раза быстрее, чем в первой версии, которая добавила SET search_path = public, pg_temp к функции - пока я не обнаружил, что словарь также может быть дополнен схемой. Тем не менее (Postgres 12) не слишком очевидно из документации.

Если вам не хватает необходимых привилегий для создания функций C, вы возвращаетесь ко второй лучшей реализации: оболочка функции IMMUTABLE вокруг функции 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

Вам понравится это обновление до unaccent в Postgres 9,6:

Расширьте стандартный файл contrib/unaccent unaccent.rules для обработки всех диакритические знаки известны Юникоду и правильно расширяют лигатуры (Томас Мунро, Леонард Бенедетти)

Жирный акцент мой. Теперь мы получаем:

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

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

Сопоставление с образцом

Для LIKE или ILIKE с произвольными шаблонами объедините это с модулем pg_trgm в PostgreSQL 9.1 или более поздней версии. Создайте триграмму 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 также предоставляет полезные операторы для "сходства" (%) и "расстояния" (<->).

Индексы триграмм также поддерживают простые регулярные выражения с ~ и соавт. и регистр без учета регистра с ILIKE:

Ответ 2

Нет, PostgreSQL не поддерживает сортировки в этом смысле

PostgreSQL не поддерживает такие сортировки (без акцента или нет), потому что никакое сравнение не может возвращаться равным, если только вещи не являются двоичными. Это связано с тем, что внутренне это создало бы множество сложностей для таких вещей, как хэш-индекс. По этой причине сопоставления в их самом строгом смысле влияют только на порядок, а не на равенство.

обходные

Полнотекстовый словарь поиска, который не принимает лексемы.

Для 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 модуль также может быть использован сам по себе без FTS-интеграции, для этого проверить Erwin ответ