How can I translate this unscalable query into a window function for dynamic columns?

StackOverflow https://stackoverflow.com/questions/14704205

  •  06-03-2022
  •  | 
  •  

質問

I have a query that I use to generate the total order amount for customers and group them into columns by month alongside another column that represents total order amount.

Here's the schema:

temp=# \d+ customers;
                          Table "pg_temp_2.customers"
   Column   |            Type             | Modifiers | Storage  | Description 
------------+-----------------------------+-----------+----------+-------------
 id         | integer                     | not null  | plain    | 
 created_at | timestamp without time zone |           | plain    | 
 name       | text                        |           | extended | 
Indexes:
    "customers_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "orders" CONSTRAINT "orders_customer_id_fkey" FOREIGN KEY (customer_id) REFERENCES customers(id)
Has OIDs: no


                             Table "pg_temp_2.orders"
   Column    |            Type             |   Modifiers   | Storage | Description 
-------------+-----------------------------+---------------+---------+-------------
 id          | integer                     | not null      | plain   | 
 created_at  | timestamp without time zone | default now() | plain   | 
 customer_id | integer                     |               | plain   | 
 amount      | integer                     |               | plain   | 
Indexes:
    "orders_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "orders_customer_id_fkey" FOREIGN KEY (customer_id) REFERENCES customers(id)
Has OIDs: no

For convenience, I've added the create table statements:

create temporary table customers ( id integer primary key, created_at timestamp without time zone, name text); 

create temporary table orders ( id integer primary key, created_at timestamp without time zone, customer_id integer references customers(id));

Here's the query I'm using:

SELECT
  c.name,
  sum(o.amount),
  CAST(SUM(
   CASE
        WHEN date_trunc('month', o.created_at) BETWEEN '2012-10-01' AND ('2012-11-01'::date - '1 day'::interval)
        THEN o.amount
        ELSE 0
   END
   ) / 100.0 AS MONEY) october2012,
   CAST(SUM(
   CASE
        WHEN date_trunc('month', o.created_at) BETWEEN '2012-11-01' AND ('2012-12-01'::date - '1 day'::interval)
        THEN o.amount
        ELSE 0
   END
   ) / 100.0 AS MONEY) as november2012
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
WHERE o.created_at >= '01 October 2012'
  AND o.created_At < '01 December 2012'
GROUP BY
 c.name
ORDER BY
  october2012 desc;

How can I get rid of that ugly case statement? There MUST be a more elegant way that rolls up these queries over a certain time slice. I tried to use window functions, but I've failed miserably. Any assistance would be appreciated!

I'm using postgresql 9.1

役に立ちましたか?

解決

This is not simpler but is more scalable:

select *
from crosstab($$
    with orders as (
        select
            customer_id,
            date_trunc('month', created_at) created_at,
            cast(amount / 100.0 as money) amount
        from orders
        where
            created_at >= '2012-10-01'
            and created_at < '2012-12-01'
    ), months as (
        select
            c.name,
            to_char(o.created_at, 'YYYY-MM') created_at,
            sum(o.amount) amount
        from
            orders o
            inner join
            customers c on c.id = o.customer_id
        group by 1, 2
    )
    select name, created_at, amount
    from months
    union all
    select name, 'Total', sum(amount)
    from months
    group by 1, 2
    order by 1, 2
    $$, $$
    select distinct to_char(created_at, 'YYYY-MM')
    from orders
    where
        created_at >= '2012-10-01'
        and created_at < '2012-12-01'
    union select 'Total'
    order by 1
    $$
) as (name text, "2012-10" money, "2012-11" money, "Total" money)
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top