Phil Freeman
Phil Freeman

Reputation: 256

Last three months average for each month in PostgreSQL query

I'm trying to build a query in Postgresql that will be used for a budget.

I currently have a list of data that is grouped by month.

For each month of the year I need to retrieve the average monthly sales from the previous three months. For example, in January I would need the average monthly sales from October through December of the previous year. So the result will be something like:

1  12345.67
2  54321.56
3  242412.45

This is grouped by month number.

Here is a snippet of code from my query that will get me the current month's sales:

LEFT JOIN (SELECT SUM((sti.cost + sti.freight) * sti.case_qty * sti.release_qty)
                  AS trsf_cost,
                  DATE_PART('month', st.invoice_dt) as month
             FROM stransitem sti, 
                  stocktrans st
            WHERE sti.invoice_no = st.invoice_no 
              AND st.invoice_dt >= date_trunc('year', current_date) 
              AND st.location_cd = 'SLC' 
              AND st.order_st != 'DEL'
         GROUP BY month) as trsf_cogs ON trsf_cogs.month = totals.month

I need another join that will get me the same thing, only averaged from the previous 3 months, but I'm not sure how.

This will ALWAYS be a January-December (1-12) list, starting with January and ending with December.

Upvotes: 4

Views: 3196

Answers (1)

Erwin Brandstetter
Erwin Brandstetter

Reputation: 656666

This is a classic problem for a window function. Here is how to solve this:

SELECT month_nr
      ,(COALESCE(m1, 0)
      + COALESCE(m2, 0)
      + COALESCE(m3, 0))
      /
      NULLIF ( CASE WHEN m1 IS NULL THEN 0 ELSE 1 END
             + CASE WHEN m2 IS NULL THEN 0 ELSE 1 END
             + CASE WHEN m3 IS NULL THEN 0 ELSE 1 END, 0) AS avg_prev_3_months
      -- or divide by 3 if 3 previous months are guaranteed or you don't care
FROM   (
    SELECT date_part('month', month) as month_nr
          ,lag(trsf_cost, 1) OVER w AS m1
          ,lag(trsf_cost, 2) OVER w AS m2
          ,lag(trsf_cost, 3) OVER w AS m3
    FROM  (
        SELECT date_part( 'month', month) as trsf_cost -- some dummy nr. for demo
                          ,month
        FROM   generate_series('2010-01-01 0:0'::timestamp
                              ,'2012-01-01 0:0'::timestamp, '1 month') month
        ) x
    WINDOW w AS (ORDER BY month)
    ) y;

This is requires that no month is ever missing! Else, have a look at this related answer:
How to compare the current row with next and previous row in PostgreSQL?

Calculates correct average for every month. If only two previous moths then devide by 2, etc. If no prev. months, result is NULL.

In your subquery, use

date_trunc('month', st.invoice_dt)::date AS month

instead of

DATE_PART('month', st.invoice_dt) as month

so you can sort months over the years easily!

More info

Upvotes: 1

Related Questions