PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Funzioni della finestra o espressioni di tabelle comuni:conta le righe precedenti all'interno dell'intervallo

Non penso che tu possa farlo a buon mercato con una semplice query, CTE e funzioni della finestra:la loro definizione del frame è statica, ma hai bisogno di un frame dinamico (a seconda dei valori delle colonne).

In genere, dovrai definire con attenzione il limite inferiore e superiore della finestra:Le seguenti query escludi la riga corrente e includi il bordo inferiore.
C'è ancora una piccola differenza:la funzione include i peer precedenti della riga corrente, mentre la subquery correlata li esclude ...

Caso di prova

Usando ts invece della parola riservata date come nome della colonna.

CREATE TABLE test (
  id  bigint
, ts  timestamp
);

ROM - Domanda di Roman

Utilizzare CTE, aggregare timestamp in un array, annullare l'annidamento, contare...
Sebbene corretti, le prestazioni deteriorano drasticamente con più di una mano piena di righe. Ci sono un paio di killer di prestazioni qui. Vedi sotto.

ARR - conta gli elementi dell'array

Ho preso la domanda di Roman e ho cercato di snellirla un po':

  • Rimuovi il 2° CTE che non è necessario.
  • Trasforma il 1° CTE in subquery, che è più veloce.
  • Diretto count() invece di riaggregare in un array e contare con array_length() .

Ma la gestione dell'array è costosa e le prestazioni si deteriorano ancora gravemente con più righe.

SELECT id, ts
     , (SELECT count(*)::int - 1
        FROM   unnest(dates) x
        WHERE  x >= sub.ts - interval '1h') AS ct
FROM (
   SELECT id, ts
        , array_agg(ts) OVER(ORDER BY ts) AS dates
   FROM   test
   ) sub;

COR - sottoquery correlata

Potresti risolverlo con una semplice subquery correlata. Molto più veloce, ma comunque...

SELECT id, ts
     , (SELECT count(*)
        FROM   test t1
        WHERE  t1.ts >= t.ts - interval '1h'
        AND    t1.ts < t.ts) AS ct
FROM   test t
ORDER  BY ts;

FNC - Funzione

Scorri le righe in ordine cronologico con un row_number() nella funzione plpgsql e combinalo con un cursore sulla stessa query, coprendo l'intervallo di tempo desiderato. Quindi possiamo semplicemente sottrarre i numeri di riga:

CREATE OR REPLACE FUNCTION running_window_ct(_intv interval = '1 hour')
  RETURNS TABLE (id bigint, ts timestamp, ct int)
  LANGUAGE plpgsql AS
$func$
DECLARE
   cur   CURSOR FOR
         SELECT t.ts + _intv AS ts1, row_number() OVER (ORDER BY t.ts) AS rn
         FROM   test t ORDER BY t.ts;
   rec   record;
   rn    int;

BEGIN
   OPEN cur;
   FETCH cur INTO rec;
   ct := -1;  -- init

   FOR id, ts, rn IN
      SELECT t.id, t.ts, row_number() OVER (ORDER BY t.ts)
      FROM   test t ORDER BY t.ts
   LOOP
      IF rec.ts1 >= ts THEN
         ct := ct + 1;
      ELSE
         LOOP
            FETCH cur INTO rec;
            EXIT WHEN rec.ts1 >= ts;
         END LOOP;
         ct := rn - rec.rn;
      END IF;

      RETURN NEXT;
   END LOOP;
END
$func$;

Chiamata con intervallo predefinito di un'ora:

SELECT * FROM running_window_ct();

O con qualsiasi intervallo:

SELECT * FROM running_window_ct('2 hour - 3 second');

db<>gioca qui
Sqlfiddle vecchio

Parametro

Con la tabella sopra ho eseguito un rapido benchmark sul mio vecchio server di test:(PostgreSQL 9.1.9 su Debian).

-- TRUNCATE test;
INSERT INTO test
SELECT g, '2013-08-08'::timestamp
         + g * interval '5 min'
         + random() * 300 * interval '1 min' -- halfway realistic values
FROM   generate_series(1, 10000) g;

CREATE INDEX test_ts_idx ON test (ts);
ANALYZE test;  -- temp table needs manual analyze

Ho variato il grassetto parte per ogni corsa e ha preso il meglio di 5 con EXPLAIN ANALYZE .

100 righe
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC:1,115 ms

1000 righe
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC:8,466 ms

5000 righe
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms

100000 righe
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms

La funzione è il chiaro vincitore. È il più veloce di un ordine di grandezza e si adatta meglio.
La gestione degli array non può competere.