sql >> Database >  >> RDS >> PostgreSQL

De juiste manier om toegang te krijgen tot de laatste rij voor elke individuele identifier?

Hier is een snelle prestatievergelijking voor de zoekopdrachten die in dit bericht worden genoemd.

Huidige instellingen:

De tabel core_message heeft 10.904.283 rijen en er zijn 60.740 rijen in test_boats (of 60.740 verschillende mmsi in core_message ).

En ik gebruik PostgreSQL 11.5

Query met alleen-index scan :

1) met DISTINCT ON :

SELECT DISTINCT ON (mmsi) mmsi 
FROM core_message;

2) met behulp van RECURSIVE met 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) Een extra tabel gebruiken met 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;

Query zonder alleen-indexscan:

4) met behulp van DISTINCT ON met mmsi,time DESC INDEX :

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

5) met DISTINCT ON met achterwaartse mmsi,time UNIQUE CONSTRAINT :

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

6) met behulp van RECURSIVE met LATERAL en 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) met behulp van RECURSIVE met LATERAL en achteruit 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) Een extra tabel gebruiken met 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;

Een speciale tabel gebruiken voor het laatste bericht:

9) Hier is mijn eerste oplossing, met behulp van een aparte tabel met alleen het laatste bericht. Deze tabel wordt gevuld als er nieuwe berichten binnenkomen, maar kan ook als volgt worden aangemaakt:

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);

Dan is het verzoek om het laatste bericht te ontvangen zo simpel als dat:

SELECT * FROM core_shipinfos;

Resultaten:

Gemiddelde van meerdere zoekopdrachten (ongeveer 5 voor de snelle):

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

Conclusie:

Ik zal geen commentaar geven op de speciale tafeloplossing en zal die tot het einde bewaren.

De aanvullende tabel (test_boats ) oplossing is hier zeker de winnaar, maar de RECURSIVE oplossing is ook behoorlijk efficiënt.

Er is een enorm prestatieverschil voor de DISTINCT ON met alleen-indexscan en degene die het niet gebruikt, maar de prestatiewinst is vrij klein voor de andere efficiënte zoekopdracht.

Dit is logisch, aangezien de belangrijkste verbetering die deze zoekopdrachten met zich meebrengen, het feit is dat ze niet het hele core_message hoeven te doorlopen tabel, maar alleen op een subset van de unieke mmsi dat is aanzienlijk kleiner (60K+) in vergelijking met de core_message tafelgrootte (10M+)

Als aanvullende opmerking lijkt er geen significante verbetering in de prestaties te zijn voor de query's die de UNIQUE CONSTRAINT gebruiken als ik de mmsi,time DESC . laat vallen INDEX . Maar als ik die index weglaat, bespaar ik natuurlijk wat ruimte (deze index neemt momenteel 328 MB in beslag)

Over de speciale tafeloplossing:

Elk bericht opgeslagen in de core_message tabel bevat zowel positie-informatie (positie, snelheid, koers, enz.) EN scheepsinformatie (naam, roepnaam, afmetingen, enz.) als scheepsidentificatie (mmsi).

Om wat meer achtergrondinformatie te geven over wat ik eigenlijk probeer te doen:ik implementeer een backend om berichten op te slaan die door schepen worden verzonden via de AIS-protocol .

Dus elke unieke mmsi die ik kreeg, kreeg ik via dit protocol. Het is geen vooraf gedefinieerde lijst. Het blijft nieuwe MMSI toevoegen totdat ik alle schepen ter wereld met AIS heb gekregen.

In die context is een speciale tabel met scheepsinformatie als laatste ontvangen bericht zinvol.

Ik zou kunnen vermijden om zo'n tabel te gebruiken zoals we hebben gezien met de RECURSIVE oplossing, maar... een speciale tafel is nog steeds 50x sneller dan deze RECURSIVE oplossing.

Die speciale tabel is in feite vergelijkbaar met de test_boat tabel, met meer informatie dan alleen de mmsi veld. Zoals het is, een tabel hebben met mmsi alleen veld of een tabel met alle laatste informatie van de core_message tabel voeg dezelfde complexiteit toe aan mijn applicatie.

Uiteindelijk denk ik dat ik voor deze speciale tafel ga. Het geeft me een onverslaanbare snelheid en ik heb nog steeds de mogelijkheid om de LATERAL te gebruiken truc op core_message , wat me meer flexibiliteit geeft.



  1. Een array invoegen in een mysql-databasekolom

  2. Oracle Apex 5.1:Over het maken van een leeg invoerformulier zoals het invoerformulier bij het maken van een tabel

  3. Hoe een Oracle-functie aanroepen vanuit Hibernate met een return-parameter?

  4. FIND_IN_SET() alternatief?