Я ищу, чтобы выбрать строки на основе того, содержится ли столбец в большом списке значений, которые я передаю в виде целочисленного массива.
Вот запрос, который я сейчас использую:
SELECT item_id, other_stuff, ...
FROM (
SELECT
-- Partitioned row number as we only want N rows per id
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY start_date) AS r,
item_id, other_stuff, ...
FROM mytable
WHERE
item_id = ANY ($1) -- Integer array
AND end_date > $2
ORDER BY item_id ASC, start_date ASC, allowed ASC
) x
WHERE x.r <= 12
Таблица построена так:
Column | Type | Collation | Nullable | Default
---------------+-----------------------------+-----------+----------+---------
item_id | integer | | not null |
allowed | boolean | | not null |
start_date | timestamp without time zone | | not null |
end_date | timestamp without time zone | | not null |
...
Indexes:
"idx_dtr_query" btree (item_id, start_date, allowed, end_date)
...
Я пришел с этим индексом после того, как попробовал разные и запустил EXPLAIN
запрос. Этот был наиболее эффективным как для запросов, так и для сортировки. Вот объяснение анализа запроса:
Subquery Scan on x (cost=0.56..368945.41 rows=302230 width=73) (actual time=0.021..276.476 rows=168395 loops=1)
Filter: (x.r <= 12)
Rows Removed by Filter: 90275
-> WindowAgg (cost=0.56..357611.80 rows=906689 width=73) (actual time=0.019..248.267 rows=258670 loops=1)
-> Index Scan using idx_dtr_query on mytable (cost=0.56..339478.02 rows=906689 width=73) (actual time=0.013..130.362 rows=258670 loops=1)
Index Cond: ((item_id = ANY ('{/* 15,000 integers */}'::integer[])) AND (end_date > '2018-03-30 12:08:00'::timestamp without time zone))
Planning time: 30.349 ms
Execution time: 284.619 ms
Проблема в том, что массив int может содержать до 15 000 элементов или около того, и в этом случае запрос выполняется довольно медленно (около 800 мс на моем ноутбуке, недавний Dell XPS).
Я думал, что передача массива int в качестве параметра может быть медленной, и, учитывая, что список идентификаторов может быть заранее сохранен в базе данных, я попытался сделать это. Я сохранил их в массиве в другой таблице и использовал item_id = ANY (SELECT UNNEST(item_ids) FROM ...)
, что было медленнее, чем мой текущий подход. Я также пытался хранить их item_id IN (SELECT item_id FROM ...)
построчно и использовать , что было еще медленнее, даже с учетом только тех строк, которые соответствуют моему тесту в таблице.
Есть ли лучший способ сделать это?
Обновление: следуя комментариям Эвана , я попробовал другой подход: каждый элемент является частью нескольких групп, поэтому вместо передачи идентификаторов элементов группы я попытался добавить идентификаторы групп в mytable:
Column | Type | Collation | Nullable | Default
---------------+-----------------------------+-----------+----------+---------
item_id | integer | | not null |
allowed | boolean | | not null |
start_date | timestamp without time zone | | not null |
end_date | timestamp without time zone | | not null |
group_ids | integer[] | | not null |
...
Indexes:
"idx_dtr_query" btree (item_id, start_date, allowed, end_date)
"idx_dtr_group_ids" gin (group_ids)
...
Новый запрос ($ 1 - идентификатор целевой группы):
SELECT item_id, other_stuff, ...
FROM (
SELECT
-- Partitioned row number as we only want N rows per id
ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY start_date) AS r,
item_id, other_stuff, ...
FROM mytable
WHERE
$1 = ANY (group_ids)
AND end_date > $2
ORDER BY item_id ASC, start_date ASC, allowed ASC
) x
WHERE x.r <= 12
Объясните, проанализируйте:
Subquery Scan on x (cost=123356.60..137112.58 rows=131009 width=74) (actual time=811.337..1087.880 rows=172023 loops=1)
Filter: (x.r <= 12)
Rows Removed by Filter: 219726
-> WindowAgg (cost=123356.60..132199.73 rows=393028 width=74) (actual time=811.330..1040.121 rows=391749 loops=1)
-> Sort (cost=123356.60..124339.17 rows=393028 width=74) (actual time=811.311..868.127 rows=391749 loops=1)
Sort Key: item_id, start_date, allowed
Sort Method: external sort Disk: 29176kB
-> Seq Scan on mytable (cost=0.00..69370.90 rows=393028 width=74) (actual time=0.105..464.126 rows=391749 loops=1)
Filter: ((end_date > '2018-04-06 12:00:00'::timestamp without time zone) AND (2928 = ANY (group_ids)))
Rows Removed by Filter: 1482567
Planning time: 0.756 ms
Execution time: 1098.348 ms
Возможно, есть место для улучшений с индексами, но мне трудно понять, как Postgres использует их, поэтому я не уверен, что изменить.
источник
mytable
, около 500 000 разныхitem_id
. Для этой таблицы нет реального естественного уникального ключа, это данные, которые автоматически генерируются для повторяющихся событий. Я думаю, чтоitem_id
+start_date
+name
(поле не показано здесь) может составлять какой-то ключ.Ответы:
Да, используйте временную таблицу. Нет ничего плохого в создании индексированной временной таблицы, когда ваш запрос такой безумный.
Но даже лучше, чем это ...
Вы выбираете 3% своей базы данных индивидуально. Я должен задаться вопросом, не лучше ли вам создавать группы / теги и т.д. в самой схеме. Мне никогда не приходилось отправлять 15 000 различных идентификаторов в запросе.
источник