Postgresql如何获取一个月内不同产品总金额的最大金额?

How can I get the maximum amount of the total amounts for different products in a month in Postgresql?

我最近才开始使用 Postgresql。我有一个名为 'sales'.

的 table
create table sales
    (
        cust    varchar(20),
        prod    varchar(20),
        day integer,
        month   integer,
        year    integer,
        state   char(2),
        quant   integer
    )

insert into sales values ('Bloom', 'Pepsi', 2, 12, 2001, 'NY', 4232);
insert into sales values ('Knuth', 'Bread', 23, 5, 2005, 'PA', 4167);
insert into sales values ('Emily', 'Pepsi', 22, 1, 2006, 'CT', 4404);
insert into sales values ('Emily', 'Fruits', 11, 1, 2000, 'NJ', 4369);
insert into sales values ('Helen', 'Milk', 7, 11, 2006, 'CT', 210);
...

总共有 500 行,10 个不同的产品和 5 个不同的客户。

看起来像这样:

现在我需要找到 12 个月中每个月最“受欢迎”和最“不受欢迎”的产品(总销量最多和最少的产品)以及相应的总销量(即 SUM) (不分年份)。

结果应该是这样的:

现在只能这样写查询了:

select month,
       prod,
       sum(quant)
from sales
group by month,prod
order by month,prod;

它给了我这样的结果:

现在我需要获取每个月的最大值。例如第1个月的前10个和中的最大值,依此类推...

我还需要得到总和的最小值(不管年份)。然后水平组合...我不知道这个...

注意:对于 TLDR,请跳到最后。

您的问题是一个非常有趣的教科书案例,因为它涉及 Postgres 的多个方面。

我经常发现将问题分解为多个子问题然后将它们连接在一起以获得最终结果集非常有帮助。

在你的例子中,我看到了两个子问题:找到每个月最受欢迎的产品,以及找到每个月最不受欢迎的产品。

让我们从最受欢迎的产品开始:

WITH months AS (
  SELECT generate_series AS month
  FROM generate_series(1, 12)
)
SELECT DISTINCT ON (month)
  month,
  prod,
  SUM(quant)
FROM months
LEFT JOIN sales USING (month)
GROUP BY month, prod
ORDER BY month, sum DESC;

解释:

  • WITH 普通 table 表达式, 它充当临时 table (在查询期间)和 有助于澄清查询。如果你觉得它令人困惑,你也可以选择 对于子查询。
  • generate_series(1, 12) 是一个 Postgres function,它生成一系列整数,在本例中为 1 到 12。
  • LEFT JOIN 允许我们将每次销售与相应的月份相关联。如果找不到给定月份的销售,则返回包含月份的行和具有 NULL 值的连接列。可以找到有关连接的更多信息 here。在您的情况下,使用 LEFT JOIN 很重要,因为使用 INNER JOIN 会排除从未售出的产品(在这种情况下应该是最不受欢迎的产品)。
  • GROUP BY用于对数量求和。
  • 在这个阶段,您应该 - 可能 - 在任何给定月份拥有多种产品。我们只想保留每个月数量最多的那些。 DISTINCT ON 对此特别有用。给定一列,它允许我们保留每个值的第一次迭代。因此,重要的是 ORDER 首先按总和计算销售额,因为只会选择第一个。我们首先想要更大的数字,所以应该使用 DESC(降序)。

我们现在可以对最不受欢迎的产品重复该过程:

WITH months AS (
  SELECT generate_series AS month
  FROM generate_series(1, 12)
)
SELECT DISTINCT ON (month)
  month,
  prod,
  SUM(quant)
FROM months
LEFT JOIN sales USING (month)
GROUP BY month, prod
ORDER BY month, sum;

结论(和 TLDR):

现在我们需要将两个查询合并为一个最终查询。

WITH months AS (
  SELECT generate_series AS month
  FROM generate_series(1, 12)
), agg_sales AS (
  SELECT
    month,
    prod,
    SUM(quant)
  FROM months
  LEFT JOIN sales USING (month)
  GROUP BY month, prod
), most_popular AS (
  SELECT DISTINCT ON (month)
    month,
    prod,
    sum
  FROM agg_sales
  ORDER BY month, sum DESC
), least_popular AS (
  SELECT DISTINCT ON (month)
    month,
    prod,
    sum
  FROM agg_sales
  ORDER BY month, sum
)
SELECT
  most_popular.month,
  most_popular.prod AS most_popular_prod,
  most_popular.sum AS most_pop_total_q,
  least_popular.prod AS least_popular_prod,
  least_popular.sum AS least_pop_total_q
FROM most_popular
  JOIN least_popular USING (month);

请注意,我使用了中间 agg_sales CTE 来尝试使查询更清晰一些并避免重复相同的操作两次,尽管这对 Postgres 的优化器来说应该不是问题。

希望您对我的回答满意。否则请不要犹豫发表评论!

编辑:虽然这个解决方案应该按原样工作,但我建议将您的日期存储为 TIMESTAMPTZ 类型的单个列。使用该类型来操作日期通常要容易得多,如果您需要进一步分析和审核数据库,这始终是一种很好的做法。

您可以通过简单地使用 EXTRACT(MONTH FROM date).

来获取任何日期的月份