Estoy buscando seleccionar filas en función de si una columna está contenida en una gran lista de valores que paso como una matriz entera.
Aquí está la consulta que uso actualmente:
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 tabla está estructurada como tal:
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)
...
Se me ocurrió este índice después de probar diferentes y ejecutar EXPLAIN
la consulta. Este fue el más eficiente tanto para consultar como para ordenar. Aquí está el análisis de explicación de la 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
El problema es que la matriz int puede contener hasta 15,000 elementos más o menos y la consulta se vuelve bastante lenta en este caso (aproximadamente 800 ms en mi computadora portátil, un Dell XPS reciente).
Pensé que pasar la matriz int como parámetro podría ser lento, y teniendo en cuenta que la lista de identificadores se puede almacenar de antemano en la base de datos, intenté hacer esto. Los almacené en una matriz en otra tabla y los usé item_id = ANY (SELECT UNNEST(item_ids) FROM ...)
, que era más lento que mi enfoque actual. También intenté almacenarlos fila por fila y usarlos item_id IN (SELECT item_id FROM ...)
, lo que fue aún más lento, incluso con solo las filas relevantes para mi caso de prueba en la tabla.
¿Hay una mejor manera de hacer esto?
Actualización: siguiendo los comentarios de Evan , probé otro enfoque: cada elemento es parte de varios grupos, así que en lugar de pasar los identificadores de ítems del grupo, intenté agregar los identificadores de grupo en 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)
...
Nueva consulta ($ 1 es la identificación del grupo objetivo):
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 analizar:
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
Puede haber margen de mejora con los índices, pero me cuesta entender cómo los utiliza Postgres, por lo que no estoy seguro de qué cambiar.
fuente
mytable
, con aproximadamente 500k diferentesitem_id
. No hay una clave única natural real para esta tabla, son los datos que se generan automáticamente para repetir eventos. Supongo que elitem_id
+start_date
+name
(campo no mostrado aquí) podría constituir algún tipo de clave.Respuestas:
Sí, usa una tabla temporal. No hay nada de malo en crear una tabla temporal indexada cuando su consulta es tan loca.
Pero incluso mejor que eso ...
Estás seleccionando el 3% de tu base de datos individualmente. Tengo que preguntarme si no es mejor crear grupos / etiquetas, etc. en el esquema en sí. Nunca tuve que enviar personalmente 15,000 ID diferentes en una consulta.
fuente