Come posso ottenere l'aggregazione di una funzione di finestra in Postgres?


11

Ho una tabella contenente due colonne di permutazioni / combinazioni di array di numeri interi e una terza colonna contenente un valore, in questo modo:

CREATE TABLE foo
(
  perm integer[] NOT NULL,
  combo integer[] NOT NULL,
  value numeric NOT NULL DEFAULT 0
);
INSERT INTO foo
VALUES
( '{3,1,2}', '{1,2,3}', '1.1400' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0.9280' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,2,1}', '{1,2,3}', '0' ),
( '{3,2,1}', '{1,2,3}', '0.8000' )

Voglio scoprire la deviazione media e standard per ogni permutazione, nonché per ogni combinazione. Posso farlo con questa query:

SELECT
  f1.perm,
  f2.combo,
  f1.perm_average_value,
  f2.combo_average_value,
  f1.perm_stddev,
  f2.combo_stddev,
  f1.perm_count,
  f2.combo_count
FROM
(
  SELECT
    perm,
    combo,
    avg( value ) AS perm_average_value,
    stddev_pop( value ) AS perm_stddev,
    count( * ) AS perm_count
  FROM foo
  GROUP BY perm, combo
) AS f1
JOIN
(
  SELECT
    combo,
    avg( value ) AS combo_average_value,
    stddev_pop( value ) AS combo_stddev,
    count( * ) AS combo_count
  FROM foo
  GROUP BY combo
) AS f2 ON ( f1.combo = f2.combo );

Tuttavia, questa query può diventare piuttosto lenta quando ho molti dati, perché la tabella "pippo" (che in realtà è composta da 14 partizioni ciascuna con circa 4 milioni di righe) deve essere scansionata due volte.

Di recente, ho appreso che Postgres supporta "Window Window", che è sostanzialmente come un GROUP BY per una particolare colonna. Ho modificato la mia query per usarli in questo modo:

SELECT
  perm,
  combo,
  avg( value ) as perm_average_value,
  avg( avg( value ) ) over w_combo AS combo_average_value,
  stddev_pop( value ) as perm_stddev,
  stddev_pop( avg( value ) ) over w_combo as combo_stddev,
  count( * ) as perm_count,
  sum( count( * ) ) over w_combo AS combo_count
FROM foo
GROUP BY perm, combo
WINDOW w_combo AS ( PARTITION BY combo );

Mentre questo funziona per la colonna "combo_count", le colonne "combo_average_value" e "combo_stddev" non sono più accurate. Sembra che la media sia presa per ogni permutazione e quindi mediata una seconda volta per ogni combinazione, il che non è corretto.

Come posso risolvere questo problema? Le funzioni della finestra possono anche essere utilizzate come ottimizzazione qui?


Supponendo la versione corrente Postgres 9.2? Le funzioni della finestra sono arrivate con 8.4.
Erwin Brandstetter,

Scusa, ho dimenticato di specificare. Sì, sto usando l'ultima versione di Postgres 9.2.4.
Scott Small,

Risposte:


9

È possibile avere funzioni di finestra sul risultato di funzioni di aggregazione in un singolo livello di query.

Funzionerebbe tutto bene dopo alcune modifiche, tranne per il fatto che fallisce per la deviazione standard sul principio matematico . I calcoli coinvolti non sono lineari, quindi non è possibile semplicemente combinare le deviazioni standard delle sottopopolazioni.

SELECT perm
      ,combo
      ,avg(value)                 AS perm_average_value
      ,sum(avg(value) * count(*)) OVER w_combo /
       sum(count(*)) OVER w_combo AS combo_average_value
      ,stddev_pop(value)          AS perm_stddev
      ,0                          AS combo_stddev  -- doesn't work!
      ,count(*)                   AS perm_count
      ,sum(count(*)) OVER w_combo AS combo_count
FROM   foo
GROUP  BY perm, combo
WINDOW w_combo  AS (PARTITION BY combo);

Perché combo_average_valueavresti bisogno di questa espressione

sum(avg(value) * count(*)) OVER w_combo / sum(count(*)) OVER w_combo

Dal momento che hai bisogno di una media ponderata . (La media di un gruppo con 10 membri pesa di più rispetto alla media di un gruppo con solo 2 membri!)

Questo funziona :

SELECT DISTINCT ON (perm, combo)
       perm
      ,combo
      ,avg(value)        OVER wpc AS perm_average_value
      ,avg(value)        OVER wc  AS combo_average_value
      ,stddev_pop(value) OVER wpc AS perm_stddev
      ,stddev_pop(value) OVER wc  AS combo_stddev
      ,count(*)          OVER wpc AS perm_count
      ,count(*)          OVER wc  AS combo_count
FROM   foo
WINDOW wc  AS (PARTITION BY combo)
      ,wpc AS (PARTITION BY perm, combo);

Sto usando due finestre diverse qui, e riduco le righe con DISTINCTcui viene applicato anche dopo le funzioni della finestra.

Ma dubito seriamente che sarà più veloce della tua query originale. Sono abbastanza sicuro che non lo sia.

Migliori prestazioni con layout di tabella modificato

Le matrici hanno un sovraccarico di 24 byte (lievi variazioni a seconda del tipo). Inoltre, sembra che ci siano alcuni elementi per array e molte ripetizioni. Per un tavolo enorme come il tuo sarebbe normale normalizzare lo schema. Esempio di layout:

CREATE TABLE combo ( 
  combo_id serial PRIMARY KEY
 ,combo    int[] NOT NULL
);

CREATE TABLE perm ( 
  perm_id  serial PRIMARY KEY
 ,perm     int[] NOT NULL
);

CREATE TABLE value (
  perm_id  int REFERENCES perm(perm_id)
 ,combo_id int REFERENCES combo(combo_id)
 ,value numeric NOT NULL DEFAULT 0
);

Se non è necessaria l'integrità referenziale, è possibile omettere i vincoli di chiave esterna.

La connessione a combo_idpotrebbe anche essere inserita nella tabella perm, ma in questo scenario la memorizzerei (leggermente de-normalizzata) valueper migliorare le prestazioni.

Ciò comporterebbe una dimensione di riga di 32 byte (intestazione tupla + riempimento: 24 byte, 2 x int (8 byte), nessun riempimento), oltre alla dimensione sconosciuta della numericcolonna. (Se non hai bisogno di estrema precisione, una double precisiono anche una realcolonna potrebbe fare altrettanto.)

Maggiori informazioni sull'archiviazione fisica in questa risposta correlata su SO o qui:
Configurazione di PostgreSQL per le prestazioni di lettura

Ad ogni modo, questa è solo una piccola parte di ciò che hai ora e renderebbe la tua query molto più veloce solo per dimensione. Anche il raggruppamento e l'ordinamento su interi semplici è molto più veloce.

Si farebbe prima aggregata in una sottoquery e poi iscriverti per perme comboper le migliori prestazioni.


Grazie per la risposta chiara e concisa. Hai ragione, sembrerebbe che non ci sia modo di ottenere la deviazione standard di una popolazione di sottogruppi in questo modo. Detto questo, mi piace la semplicità della tua soluzione. L'eliminazione di GROUP BY rende la query risultante molto più leggibile. Sfortunatamente, come sospettavi, la performance è scadente. Ho dovuto interrompere la query dopo aver eseguito per oltre 30 minuti.
Scott Small,

@ScottSmall: potresti fare qualcosa per le prestazioni ... vedi aggiornamento per rispondere.
Erwin Brandstetter,

Per semplificare la mia domanda, ho rimosso le colonne dalla footabella che non erano rilevanti. In realtà, ci sono molte altre colonne che non vengono utilizzate da questa query, quindi non sono convinto che la normalizzazione delle permutazioni e delle combinazioni fornirebbe un significativo aumento di velocità, per questo particolare caso d'uso.
Scott Small,

Inoltre, i valori interi che compongono ciascuna permutazione e combinazione provengono da un'altra tabella nel DB. La pre-generazione di questi dati è computazionalmente costosa. La lunghezza massima di una permanente / combo è 5, tuttavia 5Pn e 5Cn crescono abbastanza grandi per grandi valori di n (attualmente circa 1000, ma crescendo ogni giorno) ... comunque, l'ottimizzazione è una domanda di un altro giorno. Grazie ancora per tutto il tuo aiuto Erwin.
Scott Small,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.