Raggruppa i risultati delle query per mese e anno in postgresql


157

Ho la seguente tabella di database su un server 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

Vorrei creare una query che fornisca SUMla Salescolonna e raggruppa i risultati per mese e anno come segue:

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

C'è un modo semplice per farlo?

Risposte:


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

Su richiesta di Radu, spiegherò quella domanda:

to_char(date,'Mon') as mon, : converte l'attributo "data" nel formato definito della forma breve del mese.

extract(year from date) as yyyy : La funzione "Estrai" di Postgresql viene utilizzata per estrarre l'anno AAAA dall'attributo "data".

sum("Sales") as "Sales" : La funzione SUM () aggiunge tutti i valori "Vendite" e fornisce un alias con distinzione tra maiuscole e minuscole, con la distinzione tra maiuscole e minuscole mantenuta utilizzando le virgolette doppie.

group by 1,2: La funzione GROUP BY deve contenere tutte le colonne dell'elenco SELECT che non fanno parte dell'aggregato (ovvero tutte le colonne non presenti nelle funzioni SUM / AVG / MIN / MAX ecc.). Questo dice alla query che SUM () dovrebbe essere applicato per ogni combinazione unica di colonne, che in questo caso sono le colonne del mese e dell'anno. La parte "1,2" è una scorciatoia invece di usare gli alias di colonna, anche se è probabilmente meglio usare le espressioni "to_char (...)" e "extract (...)" complete per leggibilità.


5
Non credo che dare una risposta senza una spiegazione sia un'ottima idea, soprattutto per i principianti. Avresti dovuto spiegare la logica dietro la tua risposta, forse almeno un po '(anche se può sembrare semplice e chiaro per il resto di noi).
Radu Gheorghiu,

1
@BurakArslan I risultati sembravano esattamente ciò che l'OP aveva richiesto?
bma,

2
@rogerdpack, l'output di date_truncnon è esattamente quello che voleva il richiedente: select date_trunc('month', timestamp '2001-02-16 20:38:40')::date=>2001-02-01
pisaruk

2
Mi piace l'idea di utilizzare date_truncnella group byclausola.
pisaruk,

1
Possibili problemi "campo deve essere in gruppo per clausola" ... È meglio usare OVER (PARTITION BY).
Zon,

318

Non riesco a credere che la risposta accettata abbia così tanti voti: è un metodo orribile.

Ecco il modo corretto di farlo, con date_trunc :

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

È una cattiva pratica, ma potresti essere perdonato se lo usi

 GROUP BY 1

in una query molto semplice.

Puoi anche usare

 GROUP BY date_trunc('month', txn_date)

se non si desidera selezionare la data.


6
sfortunatamente l'output di date_truncnon è quello che si aspettava il richiedente: select date_trunc('month', timestamp '2001-02-16 20:38:40')=> 2001-02-01 00:00:00.
pisaruk,

4
Sono d'accordo che questo metodo è migliore. Non sono sicuro, ma penso che sia anche più efficiente, in quanto esiste solo un raggruppamento anziché due. Se è necessario riformattare la data, è possibile farlo successivamente utilizzando i metodi descritti nelle altre risposte:to_char(date_trunc('month', txn_date), 'YY-Mon')
Paweł Sokołowski,

1
sì, il numero di voti per la risposta accettata è sbalorditivo. date_truncè stato creato per questo preciso scopo. non c'è motivo di creare due colonne
allenwlee,

2
Molto bella! Questa è una risposta superiore, soprattutto perché puoi anche ordinare. Upvoted!
Bobmarksie,

1
Ancora un altro esempio in cui la risposta più votata dovrebbe apparire prima della risposta accettata
Brian Risk

33

to_char in realtà ti permette di tirare fuori l'anno e il mese in un colpo solo!

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'

o nel caso dell'esempio dell'utente sopra:

select to_char(date,'YY-Mon') as year_month
       sum("Sales") as "Sales"
from some_table
group by 1;

6
Consiglio vivamente di non farlo se si dispone di una discreta quantità di dati nella tabella. Questo funziona molto peggio del date_truncmetodo quando si esegue il gruppo da. Sperimentando su un DB che ho a portata di mano, su una tabella con 270k righe, il metodo date_trunc è oltre il doppio della velocità di TO_CHAR
Chris Clark,

@ChrisClark se le prestazioni sono un problema, sono d'accordo che potrebbe avere senso usare date_trunc, ma in alcuni casi è preferibile avere una stringa di data formattata e se stai usando un data warehouse performante, il calcolo aggiuntivo potrebbe non essere un affare . Ad esempio, se stai eseguendo un rapporto di analisi rapida utilizzando redshift e in genere ci vogliono 3 secondi, una query di 6 secondi è probabilmente a posto (anche se, se stai eseguendo rapporti, il calcolo aggiuntivo potrebbe rallentare le cose di una percentuale inferiore, perché c'è un overhead computazionale più grande)
mgoldwasser

1
puoi ancora farlo - esegui la formattazione come un passaggio separato 'avvolgendo' il gruppo per query. Ad esempio, SELEZIONA to_char (d, 'AAAA-GG') DA (SELEZIONA date_trunc ('mese', d) AS "d" FROM tbl) AS foo. Il meglio di entrambi i mondi!
Chris Clark,

1
Questa soluzione è semplice ed elegante. Mi piace e nel mio caso è abbastanza veloce. Grazie per questa risposta!
Guettli,

5

C'è un altro modo per ottenere il risultato usando la funzione date_part () in 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)

Grazie


1

bma la risposta è fantastica! L'ho usato con ActiveRecords, eccolo se qualcuno ne ha bisogno in Rails:

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"
)

3
oppure puoi farlo yourscopeorclass.group("extract(year from tablename.colname)")e puoi metterlo insieme 3 volte per ottenere anno, mese, giorno
nruth

1

Dai un'occhiata all'esempio E di questo tutorial -> https://www.postgresqltutorial.com/postgresql-group-by/

Devi chiamare la funzione sul tuo GROUP BY invece di chiamare il nome dell'attributo virtuale che hai creato su select. Stavo facendo ciò che tutte le risposte sopra suggerite e stavo ricevendo un column 'year_month' does not existerrore.

Ciò che ha funzionato per me è stato:

SELECT 
    date_trunc('month', created_at), 'MM/YYYY' AS month
FROM 
    "orders"  
GROUP BY 
    date_trunc('month', created_at)

0

Postgres ha alcuni tipi di timestamp:

timestamp senza fuso orario - (preferibile archiviare timestamp UTC) Lo si trova nella memoria di database multinazionale. Il cliente in questo caso si occuperà dell'offset del fuso orario per ciascun paese.

timestamp con fuso orario - L'offset del fuso orario è già incluso nel timestamp.

In alcuni casi, il database non utilizza il fuso orario ma è comunque necessario raggruppare i record in base al fuso orario locale e all'ora legale (ad es. Https://www.timeanddate.com/time/zone/romania/bucarest )

Per aggiungere il fuso orario è possibile utilizzare questo esempio e sostituire l'offset del fuso orario con il proprio.

"your_date_column" at time zone '+03'

Per aggiungere lo scostamento dell'ora legale +1 specifico all'ora legale è necessario verificare se il timestamp cade in un ora legale estiva. Poiché tali intervalli variano con 1 o 2 giorni, userò un approssimazione che non influisce sui record di fine mese, quindi in questo caso posso ignorare l'intervallo esatto di ogni anno.

Se è necessario creare una query più precisa, è necessario aggiungere condizioni per creare più casi. Ma approssimativamente, questo funzionerà perfettamente nella suddivisione dei dati al mese rispetto al fuso orario e al SummerTime quando trovi il timestamp senza fuso orario nel tuo database:

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