PostgreSQL: как сделать «нечувствительный к регистру» запрос

339

Есть ли способ написать регистронезависимые запросы в PostgreSQL, например, я хочу, чтобы следующие 3 запроса возвращали один и тот же результат.

SELECT id FROM groups where name='administrator'

SELECT id FROM groups where name='ADMINISTRATOR'

SELECT id FROM groups where name='Administrator'
Джейм
источник
если citext поставляется с вашей установкой Postgres, попробуйте тип citext. Это текст без
учета
2
Для новичков в этом вопросе эта ссылка на официальную документацию postgres содержит все ответы, приведенные здесь, а также несколько других вариантов.
Парфянский снимок
Сэр переназначил принятый ответ на ответ, сделанный @Arun, пожалуйста. Это гораздо менее сложно и не тянет кучу неприятностей после применения.
Зелибоба

Ответы:

451

Используйте функцию LOWER для преобразования строк в нижний регистр перед сравнением.

Попробуй это:

SELECT id 
  FROM groups
 WHERE LOWER(name)=LOWER('Administrator')
Chandu
источник
92
Важно отметить, что использование LOWER (или любой функции) в столбцах предикатов - в данном случае «name» - приведет к тому, что любые индексы больше не будут доступны для поиска. Если это большая или часто запрашиваемая таблица, это может вызвать проблемы. Сравнение без учета регистра, citext или индекс на основе функций улучшат производительность.
Джордан
108
Или просто создайте индекс следующим образом: CREATE INDEX idx_groups_name ON groups lower (name);
Даниэль
19
Также укажите , varchar_pattern_opsесли вы хотите, чтобы индекс для работы с LIKE 'xxx%'запросом, то есть CREATE INDEX ix_groups_name ON groups (lower(name) varchar_pattern_ops).
sayap
10
Использование оператора ILIKE (как показано в других ответах ниже) является более простым подходом, даже несмотря на то, что это ответ с наибольшим количеством голосов.
Райан
5
Просматривая комментарии здесь, много предложений здесь предлагает ILIKE, Это будет работать but with slow response. Чтобы получить быстрый доступ к таблицам на основе результатов вычислений, я предлагаю всем, кто просто проверит это, пойти с принятым ответом. Подробнее здесь и здесь
Афолаби Олаолува Акинвуми
231

используя ILIKEвместоLIKE

SELECT id FROM groups WHERE name ILIKE 'Administrator'
Мухаммед Реза Норузи
источник
1
Обратите внимание, что ILIKEHibernate не поддерживается при использовании в Spring Boot.
AnT
@ И это работает с org.hibernate.dialect.PostgreSQL94DialectSpring Boot 2.0.6.RELEASE. Но IntelliJ жалуется на это.
Саминта Кавеиш
134

Наиболее распространенным подходом является строчная или прописная строка поиска и данных. Но есть две проблемы с этим.

  1. Работает на английском, но не на всех языках. (Возможно, даже не на большинстве языков.) Не каждая строчная буква имеет соответствующую заглавную букву; не каждая заглавная буква имеет соответствующую строчную букву.
  2. Использование таких функций, как lower () и upper (), обеспечит последовательное сканирование. Он не может использовать индексы. В моей тестовой системе использование lower () занимает в 2000 раз больше времени, чем запрос, который может использовать индекс. (Тестовые данные имеют чуть более 100 тыс. Строк.)

Существует как минимум три менее часто используемых решения, которые могут быть более эффективными.

  1. Используйте модуль citext , который в основном имитирует поведение нечувствительного к регистру типа данных. Загрузив этот модуль, вы можете создать индекс без учета регистра с помощью CREATE INDEX ON groups (name::citext);. (Но см. Ниже.)
  2. Используйте сортировку без учета регистра. Это устанавливается при инициализации базы данных. Использование сортировки без учета регистра означает, что вы можете принять практически любой формат из клиентского кода, и вы все равно получите полезные результаты. (Это также означает, что вы не можете выполнять запросы с учетом регистра. Дух.)
  3. Создать функциональный индекс. Создайте строчный индекс с помощью CREATE INDEX ON groups (LOWER(name));. Сделав это, вы можете воспользоваться индексом с такими запросами, как SELECT id FROM groups WHERE LOWER(name) = LOWER('ADMINISTRATOR');, или SELECT id FROM groups WHERE LOWER(name) = 'administrator';вы должны помнить, чтобы использовать LOWER ().

Модуль citext не предоставляет тип данных без учета регистра. Вместо этого он ведет себя так, как если бы каждая строка была в нижнем регистре. То есть он ведет себя так, как если бы вы вызывали lower()каждую строку, как в номере 3 выше. Преимущество в том, что программистам не нужно запоминать строчные буквы. Но вам нужно прочитать разделы «Поведение сравнения строк» ​​и «Ограничения» в документации, прежде чем вы решите использовать citext.

Майк Шеррилл 'Cat Recall'
источник
1
О # 1: Это не должно быть проблемой, так как это будут две разные строки (подумайте об этом, как о выполнении col = 'a'и col = 'b'). О # 2: Как вы сказали, вы можете создать индекс для выражения, так что на самом деле это не проблема. Но я согласен с вами, что изменение параметров сортировки, скорее всего, является лучшим решением.
Винсент Савард
5
Может кто-нибудь сказать мне, какие сопоставления без учета регистра встроены в PostgreSQL? Я рассматриваю это как вариант, но не могу найти что-нибудь о сортировке без учета регистра для Postgres в сети?
хорват
1
@ AnupShah: Нет, я не говорю это. Я не использую PostgreSQL в Windows. Документы 9.4 говорят следующее : «На всех платформах доступны параметры сортировки с именами по умолчанию, C и POSIX. В зависимости от поддержки операционной системы могут быть доступны дополнительные параметры сортировки». Вы можете видеть, с какими сопоставлениями PostgreSQL считает доступными select * from pg_collation;.
Майк Шеррилл 'Cat Recall'
1
@Matthieu: Это лучшее введение (и предостережение) в тему, о которой я знаю: Edge Cases, чтобы помнить . Часть 1 - Текст .
Майк Шеррилл 'Cat Recall'
1
@Matthieu: FAQ по Unicode также интересно читать. Вот почему нет уникального заглавного символа для. , ,
Майк Шеррилл 'Cat Recall'
95

Вы можете использовать ILIKE. т.е.

SELECT id FROM groups where name ILIKE 'administrator'
ADJ
источник
Это правильно и работает нормально для меня, я использую MAC OS X (Mountain Lion).
ADJ
5
Это будет работать, но с медленным ответом. Чтобы получить быстрый доступ к таблицам на основе результатов вычислений, предлагаю использовать lowerфункцию. Другие подробности
Афолаби Olaoluwa Akinwumi
1
@AfolabiOlaoluwaAkinwumi принципиально это сводится к ли вы ищете для результатов противостоящих фильтрации известных значений. В последнем случае, единый случай должен сохраняться на уровне данных, позволяя работать оператору равенства. [Персональная рекомендация - верхний паскаль для значений кода типа]
Крис Марисик
53

Вы также можете прочитать по ILIKEключевому слову. Иногда это может быть полезно, хотя и не соответствует стандарту SQL. Смотрите здесь для получения дополнительной информации: http://www.postgresql.org/docs/9.2/static/functions-matching.html

Прииду Нимре
источник
9
Здесь следует обратить внимание на злонамеренный ввод данных пользователем. Если вы выполняете запрос, как email ILIKE 'user-input-email-here', убедитесь, что экранирование ввода пользователя. В противном случае люди могут вводить символы, такие как%, которые соответствуют чему угодно.
Мэтт Де Леон
2
@MattDeLeon Привет. Хорошо сказано. Но я просто хочу спросить вас, если я буду использовать ILIKEи prepared statementsзащитит ли это меня sql injection?
13
Не уверен, я полагаю, вы хотите отправить escape-строку в подготовленный оператор.
Мэтт Де Леон
1
«Ключевое слово ILIKE можно использовать вместо LIKE, чтобы сделать сравнение без учета регистра в соответствии с активной локалью. Это не в стандарте SQL, а расширение PostgreSQL». Работает как шарм в 9.3
Алексей Дерягин
1
ILIKE медленнее, чем lower(column_name) like %expression%.
Патрик Имоса
28

Вы также можете использовать регулярные выражения POSIX, такие как

SELECT id FROM groups where name ~* 'administrator'

SELECT 'asd' ~* 'AsD' возвращается t

Джеймс Браун
источник
1
У меня была та же проблема, мне нужно было поиск без учета регистра в моей базе данных PostgreSQL. Я думал о преобразовании пользовательской строки ввода в регулярное выражение. Теперь использование ~ * вместо = или LIKE работает отлично! Мне не нужно было создавать новые индексы, столбцы или что-то еще. Конечно, поиск по регулярному выражению медленнее, чем прямое байтовое сравнение, но я не думаю, что влияние на производительность будет гораздо более значительным, чем необходимость обрабатывать два набора данных (один в нижнем или верхнем регистре только для поиска, а затем для получения соответствующего оригинала). данные из другого набора). Кроме того, это чище!
Cyberknight
1
Хорошо, но как сделать, например, с regexp_matches ()?
WKT
Согласно postgres docs: оператор ~~ эквивалентен LIKE, а ~~ * соответствует ILIKE. Также есть операторы! ~~ и! ~~ *, которые представляют NOT LIKE и NOT ILIKE соответственно. Все эти операторы специфичны для PostgreSQL.
sh4
Я столкнулся с проблемой, когда скобки включены в текст, он не работает. как: "код (LC)"
Ошан Висумперума
8

Использование ~*может значительно улучшить производительность, с функциональностью INSTR.

SELECT id FROM groups WHERE name ~* 'adm'

вернуть строки с именем, которое содержит ИЛИ равно 'adm'.

Робин Го
источник
1
Привет, Робин, добро пожаловать на ТАК. Ответ Джеймса Брауна уже предложил это решение. Кроме того, предложенный вами ответ никоим образом не использует регулярные выражения.
Рафаэль