2012-01-13 12 views
10

Sto provando a mettere insieme una query che recupererà le statistiche di un utente (profitti/perdite) come risultato cumulativo, per un periodo di tempo.Funzione finestra Postgres e gruppo per eccezione

Ecco la domanda che ho finora:

SELECT p.name, e.date, 
    sum(sp.payout) OVER (ORDER BY e.date) 
    - sum(s.buyin) OVER (ORDER BY e.date) AS "Profit/Loss" 
FROM result r 
    JOIN game g ON r.game_id = g.game_id 
    JOIN event e ON g.event_id = e.event_id 
    JOIN structure s ON g.structure_id = s.structure_id 
    JOIN structure_payout sp ON g.structure_id = sp.structure_id 
          AND r.position = sp.position 
    JOIN player p ON r.player_id = p.player_id 
WHERE p.player_id = 17 
GROUP BY p.name, e.date, e.event_id, sp.payout, s.buyin 
ORDER BY p.name, e.date ASC 

La query verrà eseguito. Tuttavia, il risultato è leggermente errato. Il motivo è che uno event può avere più giochi (con diverso sp.payouts). Pertanto, quanto sopra risulta con più righe se un utente ha 2 risultati in un evento con pagamenti diversi (cioè ci sono 4 giochi per evento, e un utente ottiene £ 20 da uno e £ 40 da un altro).

La soluzione ovvia sarebbe quella di modificare il GROUP BY a:

GROUP BY p.name, e.date, e.event_id 

Tuttavia, Postgres lamenta a questo dato che non sembra essere riconoscendo che sp.payout e s.buyin sono all'interno di una funzione di aggregazione. Ho ricevuto l'errore:

column "sp.payout" must appear in the GROUP BY clause or be used in an aggregate function

Sono in esecuzione 9.1 su server Ubuntu Linux.
Mi manca qualcosa o potrebbe essere un vero difetto in Postgres?

risposta

21

Sei non, infatti, utilizzando le funzioni di aggregazione. Si utilizza window functions. Ecco perché PostgreSQL richiede sp.payout e s.buyin da includere nella clausola GROUP BY.

Aggiungendo una clausola OVER, la funzione di aggregazione sum() è trasformato in una funzione finestra, che aggrega i valori per partizione pur mantenendo tutte le righe.

È possibile combinare le funzioni della finestra e le funzioni di aggregazione. Aggregazioni vengono applicate per prime. Non ho capito dalla tua descrizione come vuoi gestire più pagamenti/buy-in per evento. A titolo di ipotesi, ne calcolo una somma per evento. Ora posso rimuovere sp.payout e s.buyin dalla clausola GROUP BY e ottenere una riga per player e event:

SELECT p.name 
    , e.event_id 
    , e.date 
    , sum(sum(sp.payout)) OVER w 
    - sum(sum(s.buyin )) OVER w AS "Profit/Loss" 
FROM player   p 
JOIN result   r ON r.player_id  = p.player_id 
JOIN game    g ON g.game_id  = r.game_id 
JOIN event    e ON e.event_id  = g.event_id 
JOIN structure   s ON s.structure_id = g.structure_id 
JOIN structure_payout sp ON sp.structure_id = g.structure_id 
          AND sp.position  = r.position 
WHERE p.player_id = 17 
GROUP BY e.event_id 
WINDOW w AS (ORDER BY e.date, e.event_id) 
ORDER BY e.date, e.event_id; 

In questa espressione: sum(sum(sp.payout)) OVER w, l'esterno sum() è una funzione finestra, l'interno sum() è una funzione di aggregazione .

Supponendo che p.player_id e e.event_id sono PRIMARY KEY nelle rispettive tabelle.

Ho aggiunto e.event_id allo ORDER BY della clausola WINDOW per arrivare a un ordine di classificazione deterministico. (Potrebbero esserci più eventi nella stessa data.) Nel risultato è stato incluso anche event_id per distinguere più eventi al giorno.

Mentre la query limita ad un singolo giocatore(WHERE p.player_id = 17), non abbiamo bisogno di aggiungere p.name o p.player_id-GROUP BY e ORDER BY. Se uno dei join dovesse moltiplicare indebitamente le righe, la somma risultante sarebbe errata (parzialmente o completamente moltiplicata). Raggruppando per p.name non è stato possibile riparare la query.

Ho anche rimosso e.date dalla clausola GROUP BY. La chiave primaria e.event_id copre tutte le colonne della riga ingresso since PostgreSQL 9.1.

Se si modifica la query per restituire più giocatori contemporaneamente, adattare: (?)

... 
WHERE p.player_id < 17 -- example - multiple players 
GROUP BY p.name, p.player_id, e.date, e.event_id -- e.date and p.name redundant 
WINDOW w AS (ORDER BY p.name, p.player_id, e.date, e.event_id) 
ORDER BY p.name, p.player_id, e.date, e.event_id; 

A meno p.name definito unico, gruppo e l'ordine da player_id in aggiunta per ottenere risultati corretti in un ordinamento deterministico.

Ho mantenuto solo e.date e p.name in GROUP BY per avere lo stesso tipo di ordinamento in tutte le clausole, sperando in un vantaggio prestazionale. Altrimenti, puoi rimuovere le colonne lì. (Simili solo e.date nella prima query.)

+0

La prima query funziona, tuttavia, l'uscita della query non è dando i risultati richiesti. Posso vedere quale emendamento funzionerebbe in teoria, ma a Postgres non piace. Proverò quanto sopra più tardi e ti faccio sapere. tuttavia, sembra che ci saranno 2 righe nell'output della tua query se un "event_id" ha più di un importo di "pagamento". – Martin

+0

ho appena provato con le modifiche che hai suggerito, e lo fa tornare con più righe dove ci sono più valori sp.payout per un singolo event_id. – Martin

+0

@ Martin: vedere la mia risposta modificata. –