2012-01-13 11 views
10

Estoy tratando de armar una consulta que recuperará las estadísticas de un usuario (ganancia/pérdida) como un resultado acumulativo, durante un período de tiempo.Función de ventana de Postgres y grupo por excepción

Aquí es la consulta que tengo hasta ahora:

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 consulta se ejecutará. Sin embargo, el resultado es ligeramente incorrecto. La razón es que un event puede tener múltiples juegos (con diferente sp.payouts). Por lo tanto, lo anterior aparece con varias filas si un usuario tiene 2 resultados en un evento con diferentes pagos (es decir, hay 4 juegos por evento, y un usuario obtiene £ 20 de uno, y £ 40 de otro).

La solución obvia sería la de modificar la GROUP BY a:

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

Sin embargo, Postgres se queja de esto, ya que no parece estar reconociendo que sp.payout y s.buyin se encuentran dentro de una función agregada. Me sale el error:

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

Estoy ejecutando 9.1 en el servidor Ubuntu Linux.
¿Me falta algo, o podría ser esto un defecto genuino en Postgres?

Respuesta

21

Usted es no, de hecho, usa funciones de agregado. Está utilizando window functions. Es por eso que PostgreSQL exige que sp.payout y s.buyin se incluyan en la cláusula GROUP BY.

Añadiendo una cláusula de OVER, la función de agregado sum() se convierte en una función de ventana, que agrega valores por partición mientras mantener todas las filas.

Puede combinar funciones de ventana y funciones de agregado. Las agregaciones se aplican primero. No entendí por su descripción cómo desea manejar pagos/compras múltiples por evento. Como una suposición, calculo una suma de ellos por evento. Ahora puedo quitar sp.payout y s.buyin de la cláusula GROUP BY y obtener una fila por player y 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; 

En esta expresión: sum(sum(sp.payout)) OVER w, el exterior sum() es una función de ventana, el interior sum() es una función de agregado .

Suponiendo p.player_id y e.event_id son PRIMARY KEY en sus tablas respectivas.

He añadido e.event_id al ORDER BY de la WINDOW cláusula para llegar a un orden de clasificación determinista. (Puede haber múltiples eventos en la misma fecha). También se incluye event_id en el resultado para distinguir varios eventos por día.

Mientras que la consulta restringe a un solo jugador(WHERE p.player_id = 17), no necesitamos añadir p.name o p.player_id a GROUP BY y ORDER BY. Si una de las uniones multiplica las filas indebidamente, la suma resultante sería incorrecta (parcial o completamente multiplicada). Agrupando por p.name no pudo reparar la consulta.

También eliminé e.date de la GROUP BY cláusula. La clave principal e.event_id cubre todas las columnas de la fila de entrada since PostgreSQL 9.1.

Si cambia la consulta para devolver varios jugadores a la vez, adaptar: (?)

... 
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 menos p.name se define único, el grupo y el orden por player_id, además, para obtener resultados correctos en una orden de clasificación determinista.

Solo guardé e.date y p.name en GROUP BY para tener un orden de clasificación idéntico en todas las cláusulas, con la esperanza de obtener un beneficio de rendimiento. De lo contrario, puedes eliminar las columnas allí. (Similar solo para e.date en la primera consulta.)

+0

La primera consulta está funcionando, sin embargo, el resultado de la consulta no está dando los resultados requeridos. Puedo ver qué enmienda funcionaría en teoría, pero a Postgres no le gusta. Probaré lo anterior más adelante y te lo haré saber. sin embargo, parece que habrá 2 filas en el resultado de su consulta si un "event_id" tiene más de un monto de "pago". – Martin

+0

Acabo de probarlo con las modificaciones que ha sugerido, y vuelve con varias filas donde hay múltiples valores de sp.payout para un único event_id. – Martin

+0

@Martin: Vea mi respuesta enmendada. –

Cuestiones relacionadas