2012-01-13 19 views
10

Próbuję utworzyć zapytanie, które pobierze statystyki użytkownika (zysk/strata) jako wynik skumulowany w pewnym okresie.Funkcja okna Postgres i grupa według wyjątku

Oto zapytanie mam tak daleko:

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 

Zapytanie zostanie uruchomiony. Jednak wynik jest nieco niepoprawny. Powodem jest to, że event może mieć wiele gier (z różnymi sp.payouts). W związku z powyższym powyższe dane są wyświetlane z wieloma wierszami, jeśli użytkownik ma 2 wyniki w przypadku różnych wypłat (np. W jednym przypadku są 4 gry, a użytkownik otrzymuje 20 GBP od jednego, a 40 GBP od innego).

Oczywistym rozwiązaniem byłoby wprowadzenie zmian do GROUP BY do:

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

Jednak Postgres skarży się na to, ponieważ nie wydaje się być uznanie, że sp.payout i s.buyin są wewnątrz funkcji zbiorczej. Otrzymuję komunikat o błędzie:

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

Uruchomiłem 9.1 na serwerze Ubuntu Linux.
Czy brakuje mi czegoś, czy może to być prawdziwa wada Postgres?

Odpowiedz

21

Jesteś nie, w rzeczywistości za pomocą funkcji agregujących. Używasz window functions. Dlatego PostgreSQL wymaga, aby i s.buyin były zawarte w klauzuli GROUP BY.

przez dodanie klauzuli OVER, łączna funkcja sum() zamienia się w funkcji okienkującej, która podsumowuje wartości każdej partycji podczas utrzymując wszystkie wiersze.

Możesz połączyć funkcje okna i funkcje agregujące. Agregacje są stosowane jako pierwsze. Z Twojego opisu nie rozumiałem, jak chcesz obsługiwać wiele wypłat/buyinów za zdarzenie. Domyślam się, że wyliczam ich sumę na zdarzenie. Teraz mogę usunąć sp.payout i s.buyin z klauzuli GROUP BY i dostać jeden wiersz za player i 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; 

W tym wyrażeniu: sum(sum(sp.payout)) OVER w zewnętrzna sum() jest funkcja okna, wewnętrzna sum() jest funkcja kruszywo .

Zakładając p.player_id i e.event_idPRIMARY KEY w odpowiednich tabelach.

Dodałem e.event_id do ORDER BY z klauzuli WINDOW, aby dojść do deterministycznego porządku sortowania. (Może wystąpić wiele zdarzeń w tym samym dniu.) W celu rozróżnienia wielu zdarzeń na dzień uwzględniono także event_id.

Podczas kwerendy ogranicza się do pojedynczego odtwarzacza (WHERE p.player_id = 17), nie trzeba dodawać p.name lub p.player_id do GROUP BY i ORDER BY. Jeśli jedno z połączeń pomnożyłoby niepotrzebnie wiersze, wynikowa suma byłaby niepoprawna (częściowo lub całkowicie pomnożona). Grupowanie przez p.name nie może wtedy naprawić zapytania.

Usunąłem również e.date z klauzuli GROUP BY. Klucz podstawowy e.event_id obejmuje wszystkie kolumny wiersza wejściowego since PostgreSQL 9.1.

Jeśli zmienić zapytanie do powrotu wielu graczy na raz, adaptacji: (?)

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

ile p.name definiuje unikalny, grupy i porządek przez player_id dodatkowo, aby uzyskać poprawne rezultaty w formie deterministyczny porządek sortowania.

Zachowałem tylko e.date i p.name w GROUP BY, aby mieć identyczną kolejność sortowania we wszystkich klauzulach, mając nadzieję na korzyści z wydajności. W przeciwnym razie możesz usunąć tam kolumny. (Podobne w przypadku pierwszego zapytania).

+0

Pierwsze zapytanie działa, ale wynik zapytania nie daje wymaganych wyników. Widzę, co poprawka będzie działała w teorii, ale Postgresowi to się nie podoba. Spróbuję tego później i dam ci znać. Wygląda jednak na to, że w wynikach zapytania znajdą się 2 wiersze, jeśli "event_id" ma więcej niż jedną kwotę "wypłaty". – Martin

+0

Po prostu próbowałem go z poprawkami, które zasugerowałeś, i powraca z wieloma wierszami, gdzie istnieje wiele wartości sp.payout dla jednego event_id. – Martin

+0

@Martin: Zobacz moją poprawioną odpowiedź. –