Вот пример, я буду использовать сгенерированные «имена», которые на самом деле являются числами, которые должны работать одинаково ...
Обратите внимание, что если вы хотите, чтобы поиск игнорировал акценты, как это делает MySQL, вам нужно заменить lower () на функцию, которая превращает акцентированные символы в неакцентированные. Поскольку эта функция, вероятно, будет медленной, было бы лучше материализовать столбец.
Если вы хотите, чтобы LIKE использовал индекс, не забудьте использовать специальный тип индекса text_pattern_ops!
create table test (a TEXT, b TEXT, c TEXT);
INSERT INTO test SELECT n, n*3, n*7 FROM generate_series( 1,100000 ) n;
create index test_a on test( lower( a ) text_pattern_ops );
create index test_b on test( lower( b ) text_pattern_ops );
create index test_c on test( lower( c ) text_pattern_ops );
EXPLAIN ANALYZE SELECT * FROM test WHERE lower(a) LIKE '12%' OR lower(b) LIKE '12%' OR lower(c) LIKE '12%' ORDER BY lower(a) LIMIT 10;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=1081.12..1081.15 rows=10 width=17) (actual time=16.987..16.992 rows=10 loops=1)
-> Sort (cost=1081.12..1096.04 rows=5969 width=17) (actual time=16.986..16.986 rows=10 loops=1)
Sort Key: (lower(a))
Sort Method: top-N heapsort Memory: 25kB
-> Bitmap Heap Scan on test (cost=144.68..952.13 rows=5969 width=17) (actual time=1.107..13.261 rows=6405 loops=1)
Recheck Cond: ((lower(a) ~~ '12%'::text) OR (lower(b) ~~ '12%'::text) OR (lower(c) ~~ '12%'::text))
Filter: ((lower(a) ~~ '12%'::text) OR (lower(b) ~~ '12%'::text) OR (lower(c) ~~ '12%'::text))
-> BitmapOr (cost=144.68..144.68 rows=6341 width=0) (actual time=1.081..1.081 rows=0 loops=1)
-> Bitmap Index Scan on test_a (cost=0.00..22.95 rows=1068 width=0) (actual time=0.210..0.210 rows=1111 loops=1)
Index Cond: ((lower(a) ~>=~ '12'::text) AND (lower(a) ~<~ '13'::text))
-> Bitmap Index Scan on test_b (cost=0.00..81.85 rows=3758 width=0) (actual time=0.603..0.603 rows=3707 loops=1)
Index Cond: ((lower(b) ~>=~ '12'::text) AND (lower(b) ~<~ '13'::text))
-> Bitmap Index Scan on test_c (cost=0.00..35.42 rows=1515 width=0) (actual time=0.266..0.266 rows=1587 loops=1)
Index Cond: ((lower(c) ~>=~ '12'::text) AND (lower(c) ~<~ '13'::text))
Total runtime: 17.038 ms
Здесь ИЛИ превращаются в растровые операции сканирования ИЛИ, которые выполняются довольно быстро. Обратите внимание, здесь используются первые две «буквы», что дает селективность 1%. Если вы используете только одну букву, вы можете получить довольно низкую селективность и огромный список результатов, который вряд ли будет полезен, поэтому лучше подождите, пока пользователь введет 2 символа, прежде чем запускать это автозаполнение.
Использование UNION не быстрее.
Но есть еще один взлом! Смотри:
select * from (values ('a'),('b'),('A'),('Â')) foo where column1 >= 'a' AND column1 < 'b';
column1
---------
a
A
Â
ки ...
create index test_aa on test( a );
create index test_ba on test( b );
create index test_ca on test( c );
Здесь вам нужно взять строку, набранную пользователем (например, «нет»), удалить акценты и вставить ее в нижний регистр, а также увеличить последний символ (который дает вам «np»). Все имена, начинающиеся с 'no', имеют свойство name> = 'no' и name <'np' из-за правил сортировки и включают акценты. </p>
EXPLAIN ANALYZE
SELECT * FROM (SELECT * FROM test WHERE a >= '12' AND a < '13' ORDER BY a LIMIT 10) f1
UNION
SELECT * FROM (SELECT * FROM test WHERE b >= '12' AND b < '13' ORDER BY b LIMIT 10) f2
UNION
SELECT * FROM (SELECT * FROM test WHERE c >= '12' AND c < '13' ORDER BY c LIMIT 10) f3
ORDER BY a;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=27.20..27.27 rows=30 width=96) (actual time=0.451..0.456 rows=30 loops=1)
Sort Key: public.test.a
Sort Method: quicksort Memory: 27kB
-> HashAggregate (cost=26.16..26.46 rows=30 width=96) (actual time=0.399..0.404 rows=30 loops=1)
-> Append (cost=0.00..25.94 rows=30 width=96) (actual time=0.157..0.372 rows=30 loops=1)
-> Limit (cost=0.00..7.35 rows=10 width=17) (actual time=0.157..0.163 rows=10 loops=1)
-> Index Scan using test_aa on test (cost=0.00..784.85 rows=1068 width=17) (actual time=0.156..0.161 rows=10 loops=1)
Index Cond: ((a >= '12'::text) AND (a < '13'::text))
-> Limit (cost=0.00..6.96 rows=10 width=17) (actual time=0.122..0.133 rows=10 loops=1)
-> Index Scan using test_ba on test (cost=0.00..2614.31 rows=3758 width=17) (actual time=0.122..0.131 rows=10 loops=1)
Index Cond: ((b >= '12'::text) AND (b < '13'::text))
-> Limit (cost=0.00..11.03 rows=10 width=17) (actual time=0.066..0.072 rows=10 loops=1)
-> Index Scan using test_ca on test (cost=0.00..1671.03 rows=1515 width=17) (actual time=0.066..0.070 rows=10 loops=1)
Index Cond: ((c >= '12'::text) AND (c < '13'::text))
Total runtime: 0.527 ms
Это намного, намного быстрее и обладает некоторыми очень полезными свойствами.
Поскольку сортировки удалены и вместо них используется индексное сканирование (из-за LIMIT 10), запрос выполняется чрезвычайно быстро во всех случаях. Это просто не возвращает все результаты.
Если пользователь ищет имена, такие как «%», и получает 5000 результатов, этот результат для пользователя бесполезен, вместо этого он просто сделает более точный запрос, добавив букву. Таким образом, ресурсы базы данных, затраченные на вычисление и возврат 5000 результатов, были потрачены бесполезно. Намного лучше сделать быстрый, ограниченный по времени запрос, подобный этому, и отобразить «больше результатов ...», чтобы пользователь знал, что он должен ввести еще несколько букв в поле поиска.