PostgreSQL - показать данные за предыдущий год + неделя текущего года

Мне нужно сделать столбец, который бы показывал прибыль предыдущего года до недели данного года. Таким образом, текущий год будет разделен на недели, и он покажет, какова прибыль за данную неделю. Для большей ясности, допустим, прибыль в прошлом году составила 1000. Прибыль на первой неделе этого года - 100, на второй неделе - 200, на третей, неделе -100 (был убыток) и так далее. Так должно получиться так:

week1|week2|week3|
1100 |1300 |1200 |

Я пробовал:

SELECT
CASE when f1.year = DATE_PART('year', now()) THEN f1.week END as week,
profit as profit
FROM (
SELECT 
DATE_PART('week', so.date_order) as week,
DATE_PART('year', so.date_order) as year,
so.profit as profit
FROM
sale_order as so
GROUP BY
week, year, profit
WHERE
so.date_order >= date_trunc('year', now() - '1 year'::interval)::timestamp::date  and so.date_order <= date_trunc('year', now()+ '1 year'::interval)::timestamp::date-1 
)as f1
GROUP BY
week, profit
ORDER BY
week

Но это не работает так, как мне нужно, потому что это делит прибыль на каждую неделю. Я имею в виду, что он показывает только прибыль за эти недели, но мне нужна «прибыль за эти недели» + «прибыль за предыдущие годы».

Моя оконная функция запроса пытается:

(
SELECT
x.id as id,week as week, x.last_year_profit + y.running_profit as week_profit
FROM
(
SELECT
min(sol.id) as id,
 --DATE_PART('year',  so.date_order) AS calcyear, DATE_PART('week',  so.date_order) AS calcweek,
sum(sol.price_subtotal - (CASE WHEN sol.account_cost_amount != 0 THEN sol.account_cost_amount ELSE sol.purchase_price END )) as last_year_profit
-- sum(sol.price_subtotal) as price_unit, sum(sol.purchase_price) as purchase_price, sum(sol.account_cost_amount) as account_cost_amount
FROM
sale_order as so
INNER JOIN sale_order_line as sol ON (sol.order_id = so.id)
INNER JOIN res_partner as rp ON (so.partner_id = rp.id)
WHERE EXISTS (
SELECT * FROM  res_partner_category_rel rpcl
WHERE 
rpcl.partner_id=rp.id and rpcl.category_id=37
and (so.date_order >= date_trunc('year', now() - '1 year'::interval)::timestamp::date  and so.date_order <= date_trunc('year', now())::timestamp::date-1 )
and so.state != 'cancel'
)
) as x
CROSS JOIN (
SELECT
date_trunc('week', so.date_order) as week,
sum(sum(sol.price_subtotal - (CASE WHEN sol.account_cost_amount != 0 THEN sol.account_cost_amount ELSE sol.purchase_price END ))) OVER  ( ORDER BY date_trunc('week', so.date_order)) as running_profit

FROM
sale_order as so
INNER JOIN sale_order_line as sol ON (sol.order_id = so.id)
INNER JOIN res_partner as rp ON (so.partner_id = rp.id)
WHERE EXISTS (
SELECT * FROM  res_partner_category_rel rpcl
WHERE 
rpcl.partner_id=rp.id and rpcl.category_id=37
AND so.date_order >= date_trunc('year', now())::timestamp::date
AND    so.date_order <  date_trunc('year', now() + '1 year'::interval)::timestamp::date 
and so.state != 'cancel'
)
GROUP BY
week
) as y
GROUP BY
id, week,week_profit
) as f1

По какой-то причине он не разделяет прибыль по неделям, а показывает только одну строку, например:

week    |week_profit|
20130114| 1500       |

person Andrius    schedule 10.06.2013    source источник
comment
Вероятно, вам нужны оконные функции: postgresql.org/docs/9.2/static/ tutorial-window.html   -  person Denis de Bernardy    schedule 10.06.2013
comment
Не забывайте всегда включать определение таблицы для подобных вопросов. То, что вы получаете в psql с \d tbl, упрощенным для рассматриваемого Q. Или скопируйте его с панели SQL в pgAdmin.   -  person Erwin Brandstetter    schedule 10.06.2013
comment
Редактируя свой вопрос, вы, кажется, забыли о моем запросе на определения таблиц. Вы вводите новые таблицы, а определения таблицы все еще нет? Значит, вы принимаете подробные ответы как должное, но сами не отвечаете на простой запрос? Добавьте, пожалуйста, образцы данных для готового тестового случая. Желательно как SQLfiddle.   -  person Erwin Brandstetter    schedule 12.06.2013
comment
Простите за это. Тогда возникает вопрос, как мне воссоздать точную копию реальной базы данных, которую я использую? Я имею в виду, что у него много таблиц со многими отношениями и так далее. Может быть, есть способ скопировать таблицы из базы данных, чтобы воссоздать их в sqlfiddle? Или мне просто нужно вручную писать каждую таблицу, которая мне нужна, чтобы сделать пример?   -  person Andrius    schedule 12.06.2013


Ответы (1)


Базовый запрос для текущей суммы

Используйте известную агрегатную функцию sum() как оконную функцию .

SELECT week, x.last_year_profit + y.running_profit AS week_profit
FROM (         -- total last year
   SELECT sum(profit) AS last_year_profit
   FROM   sale_order
   WHERE  date_order >= date_trunc('year', now() - interval '1 year')
   AND    date_order <  date_trunc('year', now()) 
   ) x
CROSS JOIN (   -- running sum current year
   SELECT date_trunc('week', date_order) AS week
         ,sum(sum(profit)) OVER (ORDER BY date_trunc('week', date_order))
                                                        AS running_profit
   FROM   sale_order
   WHERE  date_order >= date_trunc('year', now() - interval '1 year')
   AND    date_order <  date_trunc('year', now() + interval '1 year')
   GROUP  BY 1
   ) y;

Результат:

week       | week_profit
-----------+------------
2012-01-02 | 1100
2012-01-09 | 1300
2012-01-16 | 1200
...

Продвинутая функция здесь заключается в том, что я комбинирую оконные и агрегатные функции на одном уровне запроса - даже в одном выражении (!), В результате получается этот SELECT элемент, который может выглядеть удивительно для невинного глаза:

sum(sum(profit)) OVER (ORDER BY date_trunc('week', date_order))

Подробное объяснение того, как это работает, можно найти в этом тесно связанном ответе:
оконная функция Postgres и группировка по исключению

Также обратите внимание на несколько других деталей, которые я улучшил в вашем запросе.

- ›SQLfiddle

Массив / кросс-таблица ()

Примитивный способ собрать все недели в одной строке - это объединить результат в массив:

SELECT ARRAY( 
    SELECT x.last_year_profit + y.running_profit  -- only one column
    FROM (
    -- rest like query above
   ) a

Результат:

{1100,1300,1200, ...}

Или, что более продвинуто, вы используете запрос crosstab(), как описано в этом соответствующем ответе:
PostgreSQL Запрос кросс-таблицы

Один из многих связанных ответов crossbab, касающихся, в частности, временных данных:
Запрос количества строк с разбивкой по диапазонам дат

person Erwin Brandstetter    schedule 10.06.2013
comment
Спасибо за ответ. Я пробовал этот подход, но по какой-то причине получаю результат в виде всего одной строки. Неделя отображается как datetime - 20130114T00: 00: 00, а прибыль - как общая прибыль. Похоже, он не делится на недели. Я отредактировал свой вопрос новым запросом. - person Andrius; 11.06.2013