Grupuj wyniki zapytań według miesiąca i roku w postgresql

156

Mam następującą tabelę bazy danych na serwerze Postgres:

id      date          Product Sales
1245    01/04/2013    Toys    1000     
1245    01/04/2013    Toys    2000
1231    01/02/2013    Bicycle 50000
456461  01/01/2014    Bananas 4546

Chciałbym utworzyć kwerendę, która daje SUMz Saleskolumny i grup wyniki według miesiąc i rok w następujący sposób:

Apr    2013    3000     Toys
Feb    2013    50000    Bicycle
Jan    2014    4546     Bananas

Czy jest na to prosty sposób?

Frechi
źródło

Odpowiedzi:

217
select to_char(date,'Mon') as mon,
       extract(year from date) as yyyy,
       sum("Sales") as "Sales"
from yourtable
group by 1,2

Na prośbę Radu wyjaśnię to pytanie:

to_char(date,'Mon') as mon, : konwertuje atrybut "date" na zdefiniowany format krótkiej formy miesiąca.

extract(year from date) as yyyy : Funkcja "extract" Postgresql służy do wyodrębnienia roku RRRR z atrybutu "date".

sum("Sales") as "Sales" : Funkcja SUMA () sumuje wszystkie wartości „Sprzedaż” i podaje alias z rozróżnianiem wielkości liter, z rozróżnianiem wielkości liter dzięki zastosowaniu podwójnych cudzysłowów.

group by 1,2: Funkcja GROUP BY musi zawierać wszystkie kolumny z listy SELECT, które nie są częścią agregacji (czyli wszystkie kolumny spoza funkcji SUMA / AVG / MIN / MAX itd.). Informuje to zapytanie, że funkcja SUMA () powinna zostać zastosowana dla każdej unikalnej kombinacji kolumn, którymi w tym przypadku są kolumny miesiąca i roku. Część „1,2” jest skrótem zamiast używać aliasów kolumn, chociaż prawdopodobnie najlepiej jest użyć pełnych wyrażeń „to_char (...)” i „wyodrębnij (...)” w celu zwiększenia czytelności.

bma
źródło
5
Nie sądzę, aby udzielenie odpowiedzi bez wyjaśnienia było dobrym pomysłem, szczególnie dla początkujących. Powinieneś był wyjaśnić logikę swojej odpowiedzi, może przynajmniej trochę (chociaż dla reszty z nas może się to wydawać proste i nieskomplikowane).
Radu Gheorghiu
1
@BurakArslan Czy wyniki wyglądały tak, jak konkretnie żądał OP?
bma
2
@rogerdpack, wynik date_truncnie jest dokładnie tym, czego chciał select date_trunc('month', timestamp '2001-02-16 20:38:40')::date2001-02-01
pytający
2
Podoba mi się pomysł użycia date_truncw group byklauzuli.
pisaruk
1
Możliwe problemy „pole musi znajdować się w klauzuli grupuj według” ... Lepiej użyć OVER (PARTITION BY).
Zon
317

Nie mogę uwierzyć, że zaakceptowana odpowiedź ma tyle pozytywnych opinii - to okropna metoda.

Oto poprawny sposób na zrobienie tego z date_trunc :

   SELECT date_trunc('month', txn_date) AS txn_month, sum(amount) as monthly_sum
     FROM yourtable
 GROUP BY txn_month

To zła praktyka, ale możesz otrzymać wybaczenie, jeśli używasz

 GROUP BY 1

w bardzo prostym zapytaniu.

Możesz także użyć

 GROUP BY date_trunc('month', txn_date)

jeśli nie chcesz wybierać daty.

Burak Arslan
źródło
6
niestety wynik polecenia date_truncnie jest tym, czego oczekiwał pytający: select date_trunc('month', timestamp '2001-02-16 20:38:40')=> 2001-02-01 00:00:00.
pisaruk
4
Zgadzam się, że ta metoda jest lepsza. Nie jestem pewien, ale myślę, że jest też bardziej wydajny, ponieważ jest tylko jedno grupowanie zamiast dwóch. Jeśli chcesz zmienić datę, możesz to zrobić później, korzystając z metod opisanych w innych odpowiedziach:to_char(date_trunc('month', txn_date), 'YY-Mon')
Paweł Sokołowski
1
tak, liczba głosów na zaakceptowaną odpowiedź jest zadziwiająca. date_trunczostał stworzony właśnie w tym celu. nie ma powodu, aby tworzyć dwie kolumny
allenwlee
2
Bardzo dobrze! To najlepsza odpowiedź, zwłaszcza, że ​​możesz również zamówić. Głosowano!
bobmarksie
1
Jeszcze inny przykład, w którym najbardziej pozytywna odpowiedź powinna pojawić się przed zaakceptowaną odpowiedzią
Brian Risk
33

to_char faktycznie pozwala wyciągnąć rok i miesiąc za jednym zamachem!

select to_char(date('2014-05-10'),'Mon-YY') as year_month; --'May-14'
select to_char(date('2014-05-10'),'YYYY-MM') as year_month; --'2014-05'

lub w przypadku powyższego przykładu użytkownika:

select to_char(date,'YY-Mon') as year_month
       sum("Sales") as "Sales"
from some_table
group by 1;
mgoldwasser
źródło
6
Zdecydowanie odradzałbym robienie tego, jeśli masz przyzwoitą ilość danych w swojej tabeli. Działa to znacznie gorzej niż date_truncmetoda wykonywania grupy wg. Eksperymentując z bazą danych, którą mam pod ręką, na tabeli z 270 tysiącami wierszy metoda date_trunc jest ponad dwukrotnie większa niż TO_CHAR
Chris Clark,
@ChrisClark jeśli zależy Ci na wydajności, zgadzam się, że użycie date_trunc może mieć sens, ale w niektórych przypadkach preferowane jest posiadanie sformatowanego ciągu daty, a jeśli korzystasz z wydajnej hurtowni danych, dodatkowe obliczenia mogą nie przełamać transakcji . Na przykład, jeśli uruchamiasz szybki raport analityczny z przesunięciem ku czerwieni i zwykle zajmuje to 3 sekundy, 6-sekundowe zapytanie jest prawdopodobnie w porządku (chociaż, jeśli generujesz raporty, dodatkowe obliczenia mogą spowolnić działanie o mniejszy procent, ponieważ istnieje większe obciążenie obliczeniowe)
mgoldwasser,
1
nadal możesz to zrobić - po prostu wykonaj formatowanie jako oddzielny krok, „opakowując” grupę zapytaniem. Np. SELECT to_char (d, 'RRRR-DD') FROM (SELECT date_trunc ('miesiąc', d) AS "d" FROM tbl) AS foo. Najlepsze z obu światów!
Chris Clark
1
To rozwiązanie jest proste i eleganckie. Podoba mi się iw moim przypadku jest wystarczająco szybki. Dziękuję za tę odpowiedź!
guettli
5

Istnieje inny sposób osiągnięcia wyniku za pomocą funkcji date_part () w postgres.

 SELECT date_part('month', txn_date) AS txn_month, date_part('year', txn_date) AS txn_year, sum(amount) as monthly_sum
     FROM yourtable
 GROUP BY date_part('month', txn_date)

Dzięki

Nayan
źródło
1

Odpowiedź bma jest świetna! Użyłem go z ActiveRecords, tutaj jest, jeśli ktoś potrzebuje tego w Railsach:

Model.find_by_sql(
  "SELECT TO_CHAR(created_at, 'Mon') AS month,
   EXTRACT(year from created_at) as year,
   SUM(desired_value) as desired_value
   FROM desired_table
   GROUP BY 1,2
   ORDER BY 1,2"
)
mekdigital
źródło
3
lub możesz to zrobić yourscopeorclass.group("extract(year from tablename.colname)")i możesz połączyć to razem 3 razy, aby uzyskać rok, miesiąc, dzień
nruth
1

Spójrz na przykład E tego samouczka -> https://www.postgresqltutorial.com/postgresql-group-by/

Musisz wywołać funkcję w swoim GROUP BY zamiast wywoływać nazwę atrybutu wirtualnego, który utworzyłeś w select. Robiłem to, co zalecały wszystkie powyższe odpowiedzi i otrzymywałem column 'year_month' does not existbłąd.

U mnie zadziałało:

SELECT 
    date_trunc('month', created_at), 'MM/YYYY' AS month
FROM 
    "orders"  
GROUP BY 
    date_trunc('month', created_at)
Lucas Kuhn
źródło
0

Postgres ma kilka typów znaczników czasu:

sygnatura czasowa bez strefy czasowej - (najlepiej przechowywać znaczniki czasu UTC) Można ją znaleźć w pamięci masowej wielonarodowej bazy danych. Klient w tym przypadku zajmie się przesunięciem strefy czasowej dla każdego kraju.

timestamp with timezone - Przesunięcie strefy czasowej jest już uwzględnione w sygnaturze czasowej.

W niektórych przypadkach Twoja baza danych nie korzysta ze strefy czasowej, ale nadal musisz grupować rekordy pod kątem lokalnej strefy czasowej i czasu letniego (np. Https://www.timeanddate.com/time/zone/romania/bucharest )

Aby dodać strefę czasową, możesz skorzystać z tego przykładu i zastąpić przesunięcie strefy czasowej swoim.

"your_date_column" at time zone '+03'

Aby dodać przesunięcie czasu letniego +1 specyficzne dla czasu letniego, musisz sprawdzić, czy twój znacznik czasu przypada na letni czas letni. Ponieważ te przedziały zmieniają się co 1 lub 2 dni, użyję przybliżenia, które nie wpływa na zapisy na koniec miesiąca, więc w tym przypadku mogę zignorować dokładny interwał każdego roku.

Jeśli trzeba zbudować bardziej precyzyjne zapytanie, musisz dodać warunki, aby utworzyć więcej przypadków. Ale z grubsza będzie to działać dobrze przy dzieleniu danych na miesiąc według strefy czasowej i czasu letniego, gdy znajdziesz w swojej bazie danych znacznik czasu bez strefy czasowej:

SELECT 
    "id", "Product", "Sale",
    date_trunc('month', 
        CASE WHEN 
            Extract(month from t."date") > 03 AND
            Extract(day from t."date") > 26 AND
            Extract(hour from t."date") > 3 AND
            Extract(month from t."date") < 10 AND
            Extract(day from t."date") < 29 AND
            Extract(hour from t."date") < 4
        THEN 
            t."date" at time zone '+03' -- Romania TimeZone offset + DST
        ELSE
            t."date" at time zone '+02' -- Romania TimeZone offset 
        END) as "date"
FROM 
    public."Table" AS t
WHERE 1=1
    AND t."date" >= '01/07/2015 00:00:00'::TIMESTAMP WITHOUT TIME ZONE
    AND t."date" < '01/07/2017 00:00:00'::TIMESTAMP WITHOUT TIME ZONE
GROUP BY date_trunc('month', 
    CASE WHEN 
        Extract(month from t."date") > 03 AND
        Extract(day from t."date") > 26 AND
        Extract(hour from t."date") > 3 AND
        Extract(month from t."date") < 10 AND
        Extract(day from t."date") < 29 AND
        Extract(hour from t."date") < 4
    THEN 
        t."date" at time zone '+03' -- Romania TimeZone offset + DST
    ELSE
        t."date" at time zone '+02' -- Romania TimeZone offset 
    END)
profimedica
źródło