web-dev-qa-db-fra.com

Dernière requête d'enregistrement efficace avec Postgresql

J'ai besoin de faire une grosse requête, mais je ne veux que les derniers enregistrements.

Pour une seule entrée, je ferais probablement quelque chose comme

SELECT * FROM table WHERE id = ? ORDER BY date DESC LIMIT 1;

Mais je dois extraire les derniers enregistrements pour un grand nombre (des milliers d'entrées) d'enregistrements, mais uniquement la dernière entrée.

Voici ce que j'ai. Ce n'est pas très efficace. Je me demandais s'il y avait une meilleure façon.

SELECT * FROM table a WHERE ID IN $LIST AND date = (SELECT max(date) FROM table b WHERE b.id = a.id);
45
Sheldon Ross

Si vous ne souhaitez pas modifier votre modèle de données, vous pouvez utiliser DISTINCT ON pour récupérer le dernier enregistrement du tableau "b" pour chaque entrée dans "a":

SELECT DISTINCT ON (a.id) *
FROM a
INNER JOIN b ON a.id=b.id
ORDER BY a.id, b.date DESC

Si vous voulez éviter un "tri" dans la requête, ajouter un index comme celui-ci pourrait vous aider, mais je ne suis pas sûr:

CREATE INDEX b_id_date ON b (id, date DESC)

SELECT DISTINCT ON (b.id) *
FROM a
INNER JOIN b ON a.id=b.id
ORDER BY b.id, b.date DESC

Alternativement, si vous souhaitez trier les enregistrements de la table "a" d'une manière ou d'une autre:

SELECT DISTINCT ON (sort_column, a.id) *
FROM a
INNER JOIN b ON a.id=b.id
ORDER BY sort_column, a.id, b.date DESC

Approches alternatives

Cependant, toutes les requêtes ci-dessus doivent toujours lire toutes les lignes référencées de la table "b", donc si vous avez beaucoup de données, cela pourrait tout de même être trop lent.

Vous pouvez créer une nouvelle table, qui ne contient que le dernier enregistrement "b" pour chaque a.id - ou même déplacer ces colonnes dans la table "a" elle-même.

45
intgr

cela pourrait être plus efficace. Différence: la requête pour la table b n'est exécutée qu'une seule fois, votre sous-requête corrélée est exécutée pour chaque ligne:

SELECT * 
FROM table a 
JOIN (SELECT ID, max(date) maxDate
        FROM table
      GROUP BY ID) b
ON a.ID = b.ID AND a.date = b.maxDate
WHERE ID IN $LIST 
35
manji

Sur la méthode - créez une petite table dérivée contenant les temps de mise à jour/insertion les plus récents sur la table a - appelez cette table a_latest. La table a_latest aura besoin d'une granularité suffisante pour répondre à vos exigences de requête spécifiques. Dans votre cas, il devrait suffire d'utiliser

CREATE TABLE 
a_latest 
( id INTEGER NOT NULL, 
  date TSTAMP NOT NULL, 
  PRIMARY KEY (id, max_time) );

Utilisez ensuite une requête similaire à celle suggérée par najmeddine:

SELECT a.* 
FROM TABLE a, TABLE a_latest 
USING ( id, date );

L'astuce consiste alors à tenir à jour la plus récente. Pour ce faire, utilisez un déclencheur sur les insertions et les mises à jour. Un déclencheur écrit en plppgsql est assez facile à écrire. Je suis heureux de fournir un exemple si vous le souhaitez.

Le point ici est que le calcul de la dernière heure de mise à jour est pris en charge lors des mises à jour elles-mêmes. Cela déplace une plus grande partie de la charge de la requête.

4
youngthing

que penses-tu de cela?

select * from (
   SELECT a.*, row_number() over (partition by a.id order by date desc) r 
   FROM table a where ID IN $LIST 
)
WHERE r=1

je l'ai beaucoup utilisé par le passé

3
unknown

Si vous avez plusieurs lignes par identifiant, vous voulez certainement une sous-requête corrélée. Cela fera 1 recherche d'index par id, mais c'est plus rapide que de trier la table entière.

Quelque chose comme :

SELECT a.id,
(SELECT max(t.date) FROM table t WHERE t.id = a.id) AS lastdate
FROM table2;

La "table2" que vous utiliserez n'est pas la table que vous mentionnez dans votre requête ci-dessus, car ici vous avez besoin d'une liste d'identifiants distincts pour de bonnes performances. Puisque vos identifiants sont probablement des FK dans une autre table, utilisez celui-ci.

1
peufeu