Eu estou olhando para selecionar linhas com base em se uma coluna está contida em uma grande lista de valores que eu passo como uma matriz inteira.
Aqui está a consulta que atualmente uso:
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
A tabela está estruturada da seguinte forma:
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)
...
Eu vim com esse índice depois de tentar diferentes e executar EXPLAIN
a consulta. Este foi o mais eficiente para consulta e classificação. Aqui está a explicação da análise da consulta:
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
O problema é que o array int pode conter até 15.000 elementos ou mais e a consulta fica bastante lenta nesse caso (cerca de 800 ms no meu laptop, um Dell XPS recente).
Eu pensei que passar o array int como parâmetro poderia ser lento, e considerando a lista de IDs pode ser armazenada previamente no banco de dados, tentei fazer isso. Guardei-os em uma matriz em outra tabela e usei item_id = ANY (SELECT UNNEST(item_ids) FROM ...)
, que era mais lento que minha abordagem atual. Também tentei armazená-los linha por linha e usando item_id IN (SELECT item_id FROM ...)
, o que era ainda mais lento, mesmo com apenas as linhas relevantes para o meu caso de teste na tabela.
Existe um jeito melhor de fazer isso?
Atualização: após os comentários de Evan , outra abordagem que tentei: cada item faz parte de vários grupos; portanto, em vez de passar os IDs dos itens do grupo, tentei adicionar os IDs do grupo na minha tabela:
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)
...
Nova consulta ($ 1 é o ID do grupo de destino):
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
Explique a análise:
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
Pode haver espaço para melhorias nos índices, mas estou tendo dificuldades para entender como o postgres os usa, por isso não tenho certeza do que mudar.
mytable
, com cerca de 500 mil diferentes item_id
. Não existe uma chave única natural real para esta tabela, são dados gerados automaticamente para eventos repetidos. Eu acho que o item_id
+ start_date
+ name
(campo não mostrado aqui) pode constituir algum tipo de chave.