h1. БыÑтрое агрегирование в PostgreSQL Возьмем такую таблицу: bc[sql]. CREATE table test (a integer, b integer); INSERT INTO test (SELECT q.a, generate_series(1,10000) AS b FROM (SELECT generate_series(1,1000) AS a) q); Ð’ ней 10 миллионов Ñтрок, но вÑего 1000 вариантов значений колонки @a@. И мы хотим выбрать Ñту Ñ‚Ñ‹ÑÑчу вариантов значений колонки @a@. Сделаем по Ñтой колонке Ð¸Ð½Ð´ÐµÐºÑ â€” в надежде, что Ñто нам поможет: bc[sql]. CREATE INDEX index_a_on_test ON test (a); И выбираем Ð´Ð²ÑƒÐ¼Ñ Ñамыми очевидными ÑпоÑобами. bc[sql]. => \timing Timing is on. => SELECT a FROM test GROUP BY a; Time: 2310.102 ms => SELECT DISTINCT a FROM test; Time: 2354.694 ms Так Ñебе результат Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€ÐºÐ¸ Ñ‚Ñ‹ÑÑчи чиÑелок. Почему так долго? Да потому что Ñти запроÑÑ‹ проходÑÑ‚ по вÑем 10 миллионам Ñтрок в таблице, а потом агрегируют их. Ð’Ñе равно что @| uniq@ в bash’е напиÑать. bc[sql]. => EXPLAIN SELECT DISTINCT a FROM test; QUERY PLAN ---------------------------------------------------------------------- HashAggregate (cost=169248.00..169258.00 rows=1000 width=4) Group Key: a -> Seq Scan on test (cost=0.00..144248.00 rows=10000000 width=4) Ðаличие индекÑа никак не помогает. Ðо еÑÑ‚ÑŒ одна хитроÑÑ‚ÑŒ. ЕÑли мы Ñделаем @SELECT a FROM test ORDER BY a LIMIT 1@ — база моментально выдаÑÑ‚ Ñамое маленькое значение @a@ — 1 — иÑпользовав при Ñтом индекÑ. Дальше мы можем Ñделать @SELECT a FROM test WHERE a > 1 ORDER BY a LIMIT 1@ — и база Ñнова моментально прыгнет по индекÑу и выдаÑÑ‚ 2. Правда, делать Ñ‚Ñ‹ÑÑчу запроÑов в базу — так Ñебе идеÑ. Ðо мы можем Ñделать то же Ñамое, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ "рекурÑивный запроÑ":https://www.postgresql.org/docs/current/static/queries-with.html: bc[sql]. => WITH RECURSIVE rec AS ( SELECT a FROM ( SELECT a FROM test ORDER BY a LIMIT 1 ) t1 UNION ALL ( SELECT ( SELECT a FROM test WHERE a > rec.a ORDER BY a LIMIT 1 ) AS a FROM rec WHERE rec.a IS NOT NULL LIMIT 1 ) ) SELECT * FROM rec Time: 23.440 ms Да, Ñ Ñ‚Ð¾Ð¶Ðµ ненавижу рекурÑивные запроÑÑ‹. Ðо иногда без черной магии не обойтиÑÑŒ. Мне могут возразить, что мои проблемы в Ñтом Ñлучае из-за того, что у Ð¼ÐµÐ½Ñ Ð±Ð°Ð·Ð° денормализована, и Ñ Ð½Ðµ Ð²Ñ‹Ð½ÐµÑ ÐºÐ¾Ð»Ð¾Ð½ÐºÑƒ @a@ в отдельную таблицу. Тогда то же Ñамое можно бы было Ñделать при помощи @SELECT a FROM a_table@. Ðо такой Ñ„Ð¾ÐºÑƒÑ Ð¿Ñ€Ð¾Ð¹Ð´ÐµÑ‚ только тогда, когда @a@ — Ñто дейÑтвительно Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ð°Ñ ÑущноÑÑ‚ÑŒ, а не проÑто поле. ПредÑтавим, что в таблице @test@ мы хранили не чиÑелки, а Ñотрудников, а колонка @a@ — Ñто организации, в которых они работают. Тогда вÑе хорошо, но только потому, что организации не иÑчезают, когда мы удалÑем из них Ñотрудников. Другое дело — еÑли бы мы хранили в таблице вÑе тех же Ñотрудников, но в колонке @a@ были бы их имена. И мы бы хотели узнать, Ñколько у Ð½Ð°Ñ Ð²Ñего уникальных имен. Ð’ Ñтом Ñлучае Ñодержать отдельную таблицу Ñ Ð¸Ð¼ÐµÐ½Ð°Ð¼Ð¸ — Ñто то еще развлечение. Попробуйте забавы ради придумать, как избежать race condition’а, когда в параллельных транзакциÑÑ… удалетÑÑ Ð¿Ð¾Ñледний Олег и добавлÑетÑÑ Ð½Ð¾Ð²Ñ‹Ð¹. (ответ: при помощи уникального индекÑа и @ON CONFLICT DO NOTHING@) h2(#see-also). Смотрите также * "PostgreSQL, полнотекÑтовый поиÑк, ранжирование, JSON, или MongoDB не нужна":/ru/mongodb-не-нужна