PostgreSQL: Case insensitive search
От: yenik  
Дата: 28.07.22 07:50
Оценка:
Подскажите плс, как реализовать нечувствительный к регистру поиск по русским именам, и чтобы при этом использовался индекс.
Таблица такая.

CREATE TABLE IF NOT EXISTS public.names
(
    id integer NOT NULL,
    name character varying(50) COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT names_pkey PRIMARY KEY (id)
)
CREATE INDEX IF NOT EXISTS idx_name
    ON public.names USING btree
    (name ASC NULLS LAST)
    TABLESPACE pg_default;


Например, запрос:

SELECT name FROM names WHERE name LIKE 'АБ%'

Нужно, чтобы возвращались строки с разным регистром:

"АбвгД"
"аБВ"
"АБББ"
Re: PostgreSQL: Case insensitive search
От: vaa  
Дата: 28.07.22 09:00
Оценка: -1
Здравствуйте, yenik, Вы писали:

Y>Подскажите плс, как реализовать нечувствительный к регистру поиск по русским именам, и чтобы при этом использовался индекс.

Y>Таблица такая.

Y>
Y>CREATE TABLE IF NOT EXISTS public.names
Y>(
Y>    id integer NOT NULL,
Y>    name character varying(50) COLLATE pg_catalog."default" NOT NULL,
Y>    CONSTRAINT names_pkey PRIMARY KEY (id)
Y>)
Y>CREATE INDEX IF NOT EXISTS idx_name
Y>    ON public.names USING btree
Y>    (name ASC NULLS LAST)
Y>    TABLESPACE pg_default;
Y>


Y>Например, запрос:


Y>SELECT name FROM names WHERE name LIKE 'АБ%'


Y>Нужно, чтобы возвращались строки с разным регистром:


Y>"АбвгД"

Y>"аБВ"
Y>"АБББ"

https://duckduckgo.com/?q=postgres+collate+case+insensitive&atb=v314-1&ia=web&iax=qa
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re: PostgreSQL: Case insensitive search
От: vaa  
Дата: 28.07.22 09:01
Оценка: 4 (1) -1
Здравствуйте, yenik, Вы писали:

Y>Подскажите плс, как реализовать нечувствительный к регистру поиск по русским именам, и чтобы при этом использовался индекс.

Y>Таблица такая.

Y>
Y>CREATE TABLE IF NOT EXISTS public.names
Y>(
Y>    id integer NOT NULL,
Y>    name character varying(50) COLLATE pg_catalog."default" NOT NULL,
Y>    CONSTRAINT names_pkey PRIMARY KEY (id)
Y>)
Y>CREATE INDEX IF NOT EXISTS idx_name
Y>    ON public.names USING btree
Y>    (name ASC NULLS LAST)
Y>    TABLESPACE pg_default;
Y>


Y>Например, запрос:


Y>SELECT name FROM names WHERE name LIKE 'АБ%'



ILIKE
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[2]: PostgreSQL: Case insensitive search
От: Maniacal Россия  
Дата: 28.07.22 15:02
Оценка: :)
Здравствуйте, vaa, Вы писали:

Y>>Подскажите плс, как реализовать нечувствительный к регистру поиск по русским именам, и чтобы при этом использовался индекс.


vaa>ILIKE

vaa>



С базами данных работаю лет 20, никогда не знал про ILIKE
Re[3]: PostgreSQL: Case insensitive search
От: Alex.Che  
Дата: 28.07.22 15:13
Оценка:
Здравствуйте, Maniacal, Вы писали:

vaa>>ILIKE

vaa>>


M>

M>С базами данных работаю лет 20, никогда не знал про ILIKE

оно не стандартизировано.
кроме PostgreSQL есть пожалуй только в Azure Databricks.

а вообще, такие вещи принято решать при помощи COLLATE.
можно конечно через SIMILAR или UPPER, но индексы тогда отдыхают.
Re[4]: PostgreSQL: Case insensitive search
От: qaz77  
Дата: 28.07.22 16:14
Оценка:
Здравствуйте, Alex.Che, Вы писали:
AC>можно конечно через SIMILAR или UPPER, но индексы тогда отдыхают.

Так, стоп.
А как ILIKE может использовать индекс, построенный для case sensitive строк?
Re[5]: PostgreSQL: Case insensitive search
От: vsb Казахстан  
Дата: 28.07.22 16:22
Оценка: +1
Здравствуйте, qaz77, Вы писали:

Q>Здравствуйте, Alex.Che, Вы писали:

AC>>можно конечно через SIMILAR или UPPER, но индексы тогда отдыхают.

Q>Так, стоп.

Q>А как ILIKE может использовать индекс, построенный для case sensitive строк?

Никак. Поэтому либо хранить в lowercase и делать простой индекс, либо делать индекс по функции.
Re[6]: PostgreSQL: Case insensitive search
От: qaz77  
Дата: 28.07.22 16:27
Оценка:
Здравствуйте, vsb, Вы писали:
vsb>Никак. Поэтому либо хранить в lowercase и делать простой индекс, либо делать индекс по функции.

Ну, понятно. Т.е. делать руками...
Re[7]: PostgreSQL: Case insensitive search
От: Alex.Che  
Дата: 29.07.22 09:17
Оценка:
Здравствуйте, qaz77, Вы писали:

vsb>>Никак. Поэтому либо хранить в lowercase и делать простой индекс, либо делать индекс по функции.


Q>Ну, понятно. Т.е. делать руками...


если при создании поля указать правильный COLLATE то индекс будет подхватываться.
Re[8]: PostgreSQL: Case insensitive search
От: maxkar  
Дата: 29.07.22 11:07
Оценка: 80 (1)
Здравствуйте, Alex.Che, Вы писали:

AC>если при создании поля указать правильный COLLATE то индекс будет подхватываться.


Точно будет? С btree-индексом и префиксным like? Народ в интернете плачет, что по-умолчанию оно не работает. Да и документация по опклассам намекает, что обычный индекс в LIKE будет работать только в "C" locale. В теории collation можно задать, и она даже будет учитываться в индексе. Но нечувствительная к регистру collation будет и недетерминированной (non-deterministic). А в документации пишут, что

Note, in particular, that B-tree cannot use deduplication with indexes that use a nondeterministic collation. Also, certain operations are not possible with nondeterministic collations, such as pattern matching operations. Therefore, they should be used only in cases where they are specifically wanted.


Я не знаю, считается ли LIKE/ILIKE сопоставлением с образцом. Нужно экспериментировать.
Re: PostgreSQL: Case insensitive search
От: yenik  
Дата: 05.08.22 20:23
Оценка: 121 (4) +1
Непросто это делается.
За примером данных заходим на https://mydata.biz/ru/catalog/databases/names_db и скачиваем БД русских имён и фамилий.
Заливаем это в PostgreSQL.
Нужно найти фамилию по первым буквам без учёта регистра. Насколько я понял, собаководы рекомендуют LOWER. Или UPPER, что то же самое.

SELECT * FROM russian_surnames WHERE LOWER(surname) = 'петров';

"id"    "surname"    "sex"    "peoplescount"    "whenpeoplescount"    "source"
199924    "Петров"        334000    "2016-06-06 04:35:16"    "myData.biz"


Итак, запрос работает.
Посмотрим план.

test=# EXPLAIN SELECT * FROM russian_surnames WHERE surname = 'петров';
                                    QUERY PLAN
-----------------------------------------------------------------------------------
 Gather  (cost=1000.00..5863.82 rows=1 width=52)
   Workers Planned: 2
   ->  Parallel Seq Scan on russian_surnames  (cost=0.00..4863.72 rows=1 width=52)
         Filter: ((surname)::text = '??????'::text)
(4 rows)


Индексов нет, поэтому сканирование по таблице — Seq Scan.

Поскольку запрос с LOWER, то создаём индекс по функции LOWER.

test=# CREATE INDEX russian_surnames_lower_idx ON russian_surnames (LOWER(surname));
test=# EXPLAIN SELECT * FROM russian_surnames WHERE LOWER(surname) = 'петров';
                                         QUERY PLAN
---------------------------------------------------------------------------------------------
 Bitmap Heap Scan on russian_surnames  (cost=44.76..2757.27 rows=1592 width=52)
   Recheck Cond: (lower((surname)::text) = '??????'::text)
   ->  Bitmap Index Scan on russian_surnames_lower_idx  (cost=0.00..44.36 rows=1592 width=0)
         Index Cond: (lower((surname)::text) = '??????'::text)
(4 rows)


Индекс работает — Index Scan.

Но нам нужен поиск с LIKE.

test=# EXPLAIN SELECT * FROM russian_surnames WHERE LOWER(surname) LIKE 'петро%';
                                     QUERY PLAN
-------------------------------------------------------------------------------------
 Gather  (cost=1000.00..6354.66 rows=1592 width=52)
   Workers Planned: 2
   ->  Parallel Seq Scan on russian_surnames  (cost=0.00..5195.46 rows=663 width=52)
         Filter: (lower((surname)::text) ~~ '?????%'::text)
(4 rows)


Опять Seq Scan — индекс не работает.
Оказывается, просто индекса недостаточно, нужна опция text_pattern_ops.

DROP INDEX russian_surnames_lower_idx;
CREATE INDEX russian_surnames_lower_idx ON russian_surnames (LOWER(surname) text_pattern_ops);


test=# EXPLAIN SELECT * FROM russian_surnames WHERE LOWER(surname) LIKE 'петро%';
                                                    QUERY PLAN
------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on russian_surnames  (cost=48.74..2761.25 rows=1592 width=52)
   Filter: (lower((surname)::text) ~~ '?????%'::text)
   ->  Bitmap Index Scan on russian_surnames_lower_idx  (cost=0.00..48.34 rows=1592 width=0)
         Index Cond: ((lower((surname)::text) ~>=~ '?????'::text) AND (lower((surname)::text) ~<~ '????@'::text))
(4 rows)


Индекс работает.
Но после SQL Server всё это выглядит странновато.
И ещё неприятно, что EF такие индексы не умеет скаффолдить и создавать штатным образом. Приходится в миграции писать raw SQL.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.