Поддерживает ли PostgreSQL параметры сортировки без учета акцента?


98

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

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

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

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


См. Этот ответ для создания словаря FTS без акцента: stackoverflow.com/a/50595181/124486
Эван Кэрролл,

Вам нужен поиск с учетом регистра или без учета регистра?
Эван Кэрролл

Ответы:


206

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

Поскольку вложение неизменяемой функции отключит встраивание функции, основывайте его на копии объявленной 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файл Extend для обработки всех диакритических знаков, известных в Unicode, и правильного расширения лигатур (Томас Манро, Леонард Бенедетти)

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

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также предоставляет полезные операторы для «подобия» ( %) и «расстояния» ( <->). .

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


В вашем решении используются индексы, или мне нужно создать индекс unaccent(name)?
Даниэль Серодио

@ErwinBrandstetter В psql 9.1.4 я получаю «функции в индексном выражении должны быть помечены как IMMUTABLE», потому что функция unaccent является STABLE, а не INMUTABLE. Что вы порекомендуете?
e3matheus 04

1
@ e3matheus: Чувствуя себя виноватым за то, что я не проверил предыдущее решение, которое я предоставил, я исследовал и обновил свой ответ новым и лучшим (IMHO) решением проблемы, чем то, что витает в воздухе до сих пор.
Erwin Brandstetter

Разве сопоставление не utf8_general_ciявляется ответом на подобные проблемы?
Med

5
Ваши ответы ничем не хуже документации Postgres: феноменально!
electrotype

6

Нет, 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-интеграции, для этого заканчивал ответ Эрвины


2

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


1
Поддержка новых параметров сортировки в настоящее время в основном ограничена оболочками и псевдонимами для локалей операционной системы. Это очень просто. Нет поддержки функций фильтрации, настраиваемых компараторов или чего-либо из того, что вам нужно для настоящих настраиваемых сопоставлений.
Крейг Рингер,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.