Je cherche à sélectionner des lignes en fonction de la présence ou non d'une colonne dans une grande liste de valeurs que je transmets sous forme de tableau d'entiers.
Voici la requête que j'utilise actuellement:
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
Le tableau est structuré comme tel:
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)
...
J'ai trouvé cet index après avoir essayé différents et exécuté EXPLAIN
sur la requête. Celui-ci était le plus efficace pour l'interrogation et le tri. Voici l'analyse expliquée de la requête:
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
Le problème est que le tableau int peut contenir jusqu'à 15 000 éléments environ et la requête devient assez lente dans ce cas (environ 800 ms sur mon ordinateur portable, un Dell XPS récent).
Je pensais que le passage du tableau int en tant que paramètre pouvait être lent, et compte tenu de la liste des identifiants pouvant être stockée au préalable dans la base de données, j'ai essayé de le faire. Je les ai stockés dans un tableau dans une autre table et utilisé item_id = ANY (SELECT UNNEST(item_ids) FROM ...)
, ce qui était plus lent que mon approche actuelle. J'ai également essayé de les stocker ligne par ligne et d'utiliser item_id IN (SELECT item_id FROM ...)
, ce qui était encore plus lent, même avec uniquement les lignes pertinentes pour mon cas de test dans le tableau.
Existe-t-il une meilleure façon de le faire?
Mise à jour: suite aux commentaires d'Evan , une autre approche que j'ai essayée: chaque élément fait partie de plusieurs groupes, donc au lieu de passer les identifiants des éléments du groupe, j'ai essayé d'ajouter les identifiants de groupe dans 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)
...
Nouvelle requête ($ 1 est l'ID du groupe cible):
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
Expliquez analyser:
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
Il peut y avoir des améliorations à apporter aux index, mais j'ai du mal à comprendre comment postgres les utilise, donc je ne sais pas quoi changer.
la source
mytable
, avec environ 500k différentsitem_id
. Il n'y a pas de véritable clé unique naturelle pour cette table, ce sont des données qui sont générées automatiquement pour la répétition des événements. Je suppose que leitem_id
+start_date
+name
(champ non montré ici) pourrait constituer une sorte de clé.Réponses:
Oui, utilisez une table temporaire. Il n'y a rien de mal à créer une table temporaire indexée lorsque votre requête est aussi folle.
Mais encore mieux que ça ...
Vous sélectionnez 3% de votre base de données individuellement. Je dois me demander si vous ne feriez pas mieux de créer des groupes / balises, etc. dans le schéma lui-même. Je n'ai jamais personnellement eu à envoyer 15 000 identifiants différents dans une requête.
la source