在Postgres

时间:2017-08-17 14:35:39

标签: postgresql group-by aggregate-functions window-functions generate-series

我使用Postgres每天汇总一组产品的销售额,并且不仅要知道销售何时发生,还要知道何时不进行进一步处理。

SELECT 
sd.date, 
COUNT(sd.sale_id) AS sales, 
sd.product

FROM sales_data sd
-- sales per product, per day
GROUP BY sd.product, sd.date
ORDER BY sd.product, sd.date

这会产生以下结果:

    date    | sales |       product                           
------------+-------+-------------------
 2017-08-17 |  10   | soap
 2017-08-19 |   2   | soap
 2017-08-20 |   5   | soap
 2017-08-17 |   2   | shower gel
 2017-08-21 |   1   | shower gel

正如您所看到的 - 每个产品的日期范围不是连续的,因为sales_data在某些日子里没有包含这些产品的任何信息。

我打算做的是为每个产品添加一个sales = 0行,这些产品在某个范围内的任何一天都不会出售 - 例如此处,2017-08-17和{{1之间给出类似下面的内容:

2017-08-21

在一个只有单一产品的简单情况下,似乎解决方案是使用 date | sales | product ------------+-------+------------------- 2017-08-17 | 10 | soap 2017-08-18 | 0 | soap 2017-08-19 | 2 | soap 2017-08-20 | 5 | soap 2017-08-21 | 0 | soap 2017-08-17 | 2 | shower gel 2017-08-18 | 0 | shower gel 2017-08-19 | 0 | shower gel 2017-08-20 | 0 | shower gel 2017-08-21 | 1 | shower gel ,即:

  • 使用generate_series
  • 创建完整的日期
  • generate_series()已汇总的销售数据到日期系列
  • LEFT JOIN任何COALESCE在缺失的行中计为0

我遇到的问题是,这种方法似乎不能在聚合数据中重复日期重复,因为我不仅要分组多个日期,还要分组多个产品。

感觉就像我应该能够通过窗口函数做一些狡猾的事情来解决这个问题,例如加入产品名称定义的分区上的完整日期范围 - 但我无法看到实际使用此方法的方法。

2 个答案:

答案 0 :(得分:1)

您可以使用:

WITH cte AS (
   SELECT date, s.product
   FROM  ... -- some way to generate date series
   CROSS JOIN (SELECT DISTINCT product FROM sales_data) s
)
SELECT 
    c.date,
    c.product,
    COUNT(sd.sale_id) AS sales
FROM cte c
LEFT JOIN sales_data sd
  ON c.date = sd.date AND c.product= sd.product
GROUP BY c.date, c.product
ORDER BY c.date, c.product;

首先创建日期和产品的笛卡尔积,然后LEFT JOIN创建实际数据并进行计算。

Oracle为这个名为 Partitioned Outer Joins

的方案提供了很多功能
SELECT times.time_id, product, quantity 
FROM inventory  PARTITION BY  (product) 
RIGHT OUTER JOIN times ON (times.time_id = inventory.time_id) 
WHERE times.time_id BETWEEN TO_DATE('01/04/01', 'DD/MM/YY') 
      AND TO_DATE('06/04/01', 'DD/MM/YY') 
ORDER BY  2,1; 

答案 1 :(得分:0)

select 
    date, 
    count(sale_id) as sales, 
    product
from
    sales_data
    right join (
        (
            select d::date as date
            from generate_series (
                (select min(date) from sales_data),
                (select max(date) from sales_data),
                '1 day'
            ) gs (d)
        ) gs
        cross join
        (select distinct product from sales_data) p
    ) cj using (product, date)
group by product, date
order by product, date