Sto cercando di selezionare le righe in base al fatto che una colonna sia contenuta in un ampio elenco di valori che passo come un array intero.
Ecco la query che attualmente 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
La tabella è strutturata come tale:
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)
...
Ho trovato questo indice dopo averne provato diversi e aver eseguito EXPLAIN
la query. Questo era il più efficiente sia per le query che per l'ordinamento. Ecco la spiegazione dell'analisi della query:
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
Il problema è che l'array int può contenere fino a 15.000 elementi circa e in questo caso la query diventa piuttosto lenta (circa 800 ms sul mio laptop, un Dell XPS recente).
Ho pensato che il passaggio dell'array int come parametro potesse essere lento, quindi, considerando che l'elenco di ID può essere precedentemente memorizzato nel database, ho provato a farlo. Li ho archiviati in un array in un'altra tabella e usato item_id = ANY (SELECT UNNEST(item_ids) FROM ...)
, che era più lento del mio approccio attuale. Ho anche provato a memorizzarli riga per riga e utilizzando item_id IN (SELECT item_id FROM ...)
, che era ancora più lento, anche con solo le righe relative al mio caso di test nella tabella.
C'è un modo migliore per farlo?
Aggiornamento: seguendo i commenti di Evan , ho provato un altro approccio: ogni elemento fa parte di più gruppi, quindi invece di passare gli ID degli elementi del gruppo, ho provato ad aggiungere gli ID di gruppo in 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)
...
Nuova query ($ 1 è l'id del gruppo targetizzato):
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
Spiega analizzare:
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
Potrebbero esserci margini di miglioramento con gli indici, ma faccio fatica a capire come li utilizza Postgres, quindi non sono sicuro di cosa cambiare.
mytable
, con circa 500k diversi item_id
. Non esiste una chiave unica naturale reale per questa tabella, sono i dati generati automaticamente per gli eventi ricorrenti. Suppongo che il item_id
+ start_date
+ name
(campo non mostrato qui) possa costituire una sorta di chiave.