【问题标题】:How can I get the maximum amount of the total amounts for different products in a month in Postgresql?如何在Postgresql中获得一个月内不同产品总金额的最大金额?
【发布时间】:2019-10-08 17:55:56
【问题描述】:

我最近才开始使用 Postgresql。我有一个名为“sales”的表。

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个总和中的最大值,以此类推……

我还需要得到总和的最小值(不管是什么年份)。并且将它们水平组合...我不知道这个...

【问题讨论】:

  • Sample data 更好地表示为formatted text。请参阅here 了解有关如何创建漂亮表格的一些提示。
  • 快速说明:我建议在操作日期时使用 TIMESTAMPTZ 类型或任何其他时间类型,而不是使用三个不同的行。这允许您执行 WHERE date >= NOW() - INTERVAL '1 month' 之类的操作,并避免在每个查询中从各个组件构建新日期。
  • 嗯...谢谢你的建议。但是你能写一个示例查询吗??

标签: postgresql sum max row-number case-when


【解决方案1】:

注意:对于 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 是 common table expression, 它充当临时表(在查询期间)和 有助于澄清查询。如果您觉得它令人困惑,您也可以选择 用于子查询。
  • 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)即可获取任何日期的月份。

【讨论】:

  • 太棒了!您的解决方案适用于我的 Postgresql。只是last_popular 应该是least_popular。无论如何,这对我真的很有帮助,如果我不能理解一些细节,我会问你。
  • 太好了,很高兴它对你有用!感谢您指出错字,我编辑了消息。如果您需要任何详细信息,请务必询问。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-28
  • 2013-07-02
  • 1970-01-01
  • 2021-07-30
  • 1970-01-01
相关资源
最近更新 更多