PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment accéder correctement à la dernière ligne pour chaque identifiant individuel ?

Voici une comparaison rapide des performances pour les requêtes mentionnées dans cet article.

Configuration actuelle :

Le tableau core_message a 10 904 283 lignes et il y a 60 740 lignes dans test_boats (ou 60 740 mmsi distincts dans core_message ).

Et j'utilise PostgreSQL 11.5

Requête utilisant l'analyse d'index uniquement :

1) en utilisant DISTINCT ON :

SELECT DISTINCT ON (mmsi) mmsi 
FROM core_message;

2) en utilisant RECURSIVE avec LATERAL :

WITH RECURSIVE cte AS (
   (
   SELECT mmsi
   FROM   core_message
   ORDER  BY mmsi
   LIMIT  1
   )
   UNION ALL
   SELECT m.*
   FROM   cte c
   CROSS  JOIN LATERAL (
      SELECT mmsi
      FROM   core_message
      WHERE  mmsi > c.mmsi
      ORDER  BY mmsi
      LIMIT  1
      ) m
   )
TABLE cte;

3) Utiliser une table supplémentaire avec LATERAL :

SELECT a.mmsi
FROM test_boats a
CROSS JOIN LATERAL(
    SELECT b.time
    FROM core_message b
    WHERE a.mmsi = b.mmsi
    ORDER BY b.time DESC
    LIMIT 1
) b;

Requête n'utilisant pas l'analyse d'index uniquement :

4) en utilisant DISTINCT ON avec mmsi,time DESC INDEX :

SELECT DISTINCT ON (mmsi) * 
FROM core_message 
ORDER BY mmsi, time desc;

5) en utilisant DISTINCT ON avec mmsi,time en arrière UNIQUE CONSTRAINT :

SELECT DISTINCT ON (mmsi) * 
FROM core_message 
ORDER BY mmsi desc, time desc;

6) en utilisant RECURSIVE avec LATERAL et mmsi,time DESC INDEX :

WITH RECURSIVE cte AS (
   (
   SELECT *
   FROM   core_message
   ORDER  BY mmsi , time DESC 
   LIMIT  1
   )
   UNION ALL
   SELECT m.*
   FROM   cte c
   CROSS  JOIN LATERAL (
      SELECT *
      FROM   core_message
      WHERE  mmsi > c.mmsi
      ORDER  BY mmsi , time DESC 
      LIMIT  1
      ) m
   )
TABLE cte;

7) en utilisant RECURSIVE avec LATERAL et retour mmsi,time UNIQUE CONSTRAINT :

WITH RECURSIVE cte AS (

   (

   SELECT *
   FROM   core_message
   ORDER  BY mmsi DESC , time DESC 
   LIMIT  1
   )
   UNION ALL
   SELECT m.*
   FROM   cte c
   CROSS  JOIN LATERAL (
      SELECT *
      FROM   core_message
      WHERE  mmsi < c.mmsi
      ORDER  BY mmsi DESC , time DESC 
      LIMIT  1
      ) m
   )
TABLE cte;

8) Utiliser une table supplémentaire avec LATERAL :

SELECT b.*
FROM test_boats a
CROSS JOIN LATERAL(
    SELECT b.*
    FROM core_message b
    WHERE a.mmsi = b.mmsi
    ORDER BY b.time DESC
    LIMIT 1
) b;

Utiliser une table dédiée pour le dernier message :

9) Voici ma solution initiale, en utilisant une table distincte avec uniquement le dernier message. Ce tableau est rempli au fur et à mesure de l'arrivée de nouveaux messages mais pourrait aussi être créé comme ceci :

CREATE TABLE core_shipinfos AS (
    WITH RECURSIVE cte AS (
       (
       SELECT *
       FROM   core_message
       ORDER  BY mmsi DESC , time DESC 
       LIMIT  1
       )
       UNION ALL
       SELECT m.*
       FROM   cte c
       CROSS  JOIN LATERAL (
          SELECT *
          FROM   core_message
          WHERE  mmsi < c.mmsi
          ORDER  BY mmsi DESC , time DESC 
          LIMIT  1
          ) m
       )
    TABLE cte);

Ensuite, la demande pour obtenir le dernier message est aussi simple que cela :

SELECT * FROM core_shipinfos;

Résultats :

Moyenne de plusieurs requêtes (environ 5 pour la plus rapide) :

1) 9146 ms
2) 728 ms
3) 498 ms

4) 51488 ms
5) 54764 ms
6) 729 ms
7) 778 ms
8) 516 ms

9) 15 ms

Conclusion :

Je ne commenterai pas la solution de table dédiée, et la garderai pour la fin.

La table supplémentaire (test_boats ) la solution est définitivement la gagnante ici mais la RECURSIVE solution est également assez efficace.

Il y a un énorme écart de performances pour le DISTINCT ON en utilisant l'analyse d'index uniquement et celle qui ne l'utilise pas, le gain de performances est plutôt faible pour l'autre requête efficace.

Cela a du sens car l'amélioration majeure apportée par ces requêtes est le fait qu'elles n'ont pas besoin de boucler sur l'ensemble du core_message table mais uniquement sur un sous-ensemble de l'unique mmsi qui est nettement plus petit (60K+) par rapport au core_message taille du tableau (10M+)

De plus, il ne semble pas y avoir d'amélioration significative des performances pour les requêtes utilisant la UNIQUE CONSTRAINT si je supprime le mmsi,time DESC INDEX . Mais la suppression de cet index me permettra bien sûr d'économiser de l'espace (cet index prend actuellement 328 Mo)

À propos de la solution de table dédiée :

Chaque message stocké dans le core_message table contient à la fois des informations de position (position, vitesse, cap, etc.) ET des informations sur le navire (nom, indicatif, dimensions, etc.), ainsi que l'identifiant du navire (mmsi).

Pour donner un peu plus de contexte sur ce que j'essaie de faire :j'implémente un backend pour stocker les messages émis par les vaisseaux via le Protocole AIS .

En tant que tel, chaque mmsi unique que j'ai obtenu, je l'ai obtenu via ce protocole. Ce n'est pas une liste prédéfinie. Il continue d'ajouter de nouveaux MMSI jusqu'à ce que tous les navires du monde utilisent l'AIS.

Dans ce contexte, une table dédiée avec les informations du navire comme dernier message reçu a du sens.

Je pourrais éviter d'utiliser une telle table comme nous l'avons vu avec le RECURSIVE solution, mais... une table dédiée est toujours 50x plus rapide que ce RECURSIVE solution.

Cette table dédiée est en fait similaire au test_boat table, avec plus d'informations que juste le mmsi champ. En l'état, avoir une table avec mmsi seul champ ou un tableau avec toutes les dernières informations du core_message table ajoute la même complexité à mon application.

Au final, je pense que je vais me diriger vers cette table dédiée. Cela me donnera une vitesse imbattable et j'aurai toujours la possibilité d'utiliser le LATERAL astuce sur core_message , ce qui me donnera plus de flexibilité.