Frechi
Frechi

Reputation: 2503

Group query results by month and year in postgresql

I have the following database table on a Postgres server:

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

I would like to create a query that gives the SUM of the Sales column and groups the results by month and year as follows:

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

Is there a simple way to do that?

Upvotes: 247

Views: 259629

Answers (8)

Sevinch Abdisattarova
Sevinch Abdisattarova

Reputation: 21

I also need to find results grouped by YEAR and MONTH. When I grouped them by TIMESTAMP, sum function grouped them with dates and minutes, but that wasn't what I wanted. Using this query may be helpful for you.

select sum(sum),
       concat(year, '-', month, '-', '01')::timestamp
from (select sum(t.final_price)               as sum,
             extract(year from t.created_at)  as year,
             extract(month from t.created_at) as month
      from transactions t
      where status = 'SUCCESS'
      group by t.created_at) t
group by year, month;

transactions table query result As you can see in the picture, in '2022-07-01' I have two columns in table, and in query result they are grouped together.

Upvotes: 1

jian
jian

Reputation: 4824

Why not just use date_part function. https://www.postgresql.org/docs/8.0/functions-datetime.html

SELECT date_part('year', txn_date) AS txn_year,
       date_part('month', txn_date) AS txn_month,
       sum(amount) as monthly_sum
FROM payment
GROUP BY txn_year, txn_month
order by txn_year;

Upvotes: 14

Lucas Kuhn
Lucas Kuhn

Reputation: 402

Take a look at example 6) of this tutorial -> https://www.postgresqltutorial.com/postgresql-group-by/

You need to call the function on your GROUP BY instead of calling the name of the virtual attribute you created on select. I was doing what all the answers above recommended and I was getting a column 'year_month' does not exist error.

What worked for me was:

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

Upvotes: 7

Burak Arslan
Burak Arslan

Reputation: 8001

I can't believe the accepted answer has so many upvotes -- it's a horrible method.

Here's the correct way to do it, with date_trunc:

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

It's bad practice but you might be forgiven if you use

 GROUP BY 1

in a very simple query.

You can also use

 GROUP BY date_trunc('month', txn_date)

if you don't want to select the date.

Upvotes: 479

profimedica
profimedica

Reputation: 2830

Postgres has few types of timestamps:

timestamp without timezone - (Preferable to store UTC timestamps) You find it in multinational database storage. The client in this case will take care of the timezone offset for each country.

timestamp with timezone - The timezone offset is already included in the timestamp.

In some cases, your database does not use the timezone but you still need to group records in respect with local timezone and Daylight Saving Time (e.g. https://www.timeanddate.com/time/zone/romania/bucharest)

To add timezone you can use this example and replace the timezone offset with yours.

"your_date_column" at time zone '+03'

To add the +1 Summer Time offset specific to DST you need to check if your timestamp falls into a Summer DST. As those intervals varies with 1 or 2 days, I will use an aproximation that does not affect the end of month records, so in this case i can ignore each year exact interval.

If more precise query has to be build, then you have to add conditions to create more cases. But roughly, this will work fine in splitting data per month in respect with timezone and SummerTime when you find timestamp without timezone in your 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)

Upvotes: 0

Nayan
Nayan

Reputation: 588

There is another way to achieve the result using the date_part() function 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)

Thanks

Upvotes: 7

mgoldwasser
mgoldwasser

Reputation: 15424

to_char actually lets you pull out the Year and month in one fell swoop!

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'

or in the case of the user's example above:

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

Upvotes: 60

bma
bma

Reputation: 9756

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

At the request of Radu, I will explain that query:

to_char(date,'Mon') as mon, : converts the "date" attribute into the defined format of the short form of month.

extract(year from date) as yyyy : Postgresql's "extract" function is used to extract the YYYY year from the "date" attribute.

sum("Sales") as "Sales" : The SUM() function adds up all the "Sales" values, and supplies a case-sensitive alias, with the case sensitivity maintained by using double-quotes.

group by 1,2 : The GROUP BY function must contain all columns from the SELECT list that are not part of the aggregate (aka, all columns not inside SUM/AVG/MIN/MAX etc functions). This tells the query that the SUM() should be applied for each unique combination of columns, which in this case are the month and year columns. The "1,2" part is a shorthand instead of using the column aliases, though it is probably best to use the full "to_char(...)" and "extract(...)" expressions for readability.

Upvotes: 263

Related Questions