【问题标题】:SQL query that does two GROUP BYs?执行两个 GROUP BY 的 SQL 查询?
【发布时间】:2011-05-09 21:44:30
【问题描述】:

我无法为需要生成的报告获取 SQL。我有(相当于)以下设置:

  • 一张表articles(字段,如姓名、manufacturer_id 等)。
  • 一个表sales
    • FK 到名为 article_id 的文章
    • 一个名为amount的整数
    • date 字段
    • 名为type 的字段。我们可以假设它是一个字符串,它可以有 3 个已知值 - 'morning''evening''night'

我想生成一份汇总的销售报告,给定开始日期和结束日期:

 +--------------+---------------+--------------+-------------+
 | article_name | morning_sales | evening_sales| night_sales |
 +--------------+---------------+--------------+-------------+
 | article 1    |             0 |           12 |           2 |
 | article 2    |            80 |            3 |           0 |
...            ...             ...            ...           ...
 | article n    |            37 |           12 |           1 |
 +--------------+---------------+--------------+-------------+

我想尽可能高效地做到这一点。到目前为止,我已经能够生成一个查询,该查询将为我提供一种类型的销售(早上、晚上或晚上),但我无法同时为多种类型执行此操作。有可能吗?

这是我目前所拥有的;它会给我在给定时期内所有文章的文章名称和早间销售额 - 换句话说,报告的前两列:

SELECT articles.name as article_name,
       SUM(sales.amount) as morning_sales,
FROM "sales"
INNER JOIN articles ON articles.id = sales.articles_id
WHERE ( sales.date >= '2011-05-09'
    AND sales.end_date <= '2011-05-16'
    AND sales.type = 'morning'
)
GROUP BY sales.article_id

我想我可以在晚上和晚上做同样的事情,但文章会有所不同;例如,有些文章可能在早上有销售,但在晚上没有。

  • 如果我必须为每种销售类型执行 1 个请求,我如何“混合和匹配”我将获得的不同文章列表?
  • 有没有更好的方法来做到这一点(可能使用某种子查询)?

同样,我可以构建一个查询,提供所有早、晚和晚的销售,按类型分组。我想我的问题是我需要做两个 GROUP BY 才能得到这个报告。如果可能的话,我不知道该怎么做。

我使用 PostgreSQL 作为我的数据库,但我希望尽可能保持标准。如果有帮助,使用它的应用程序是 Rails 应用程序。

【问题讨论】:

    标签: sql ruby-on-rails postgresql


    【解决方案1】:

    幸运的是,您不需要对您的数据库格式进行多次查询。这应该适合你:

    SELECT
      articles.name AS article_name
      SUM(IF(sales.type = 'morning', sales.amount, 0.0)) AS morning_sales,
      SUM(IF(sales.type = 'evening', sales.amount, 0.0)) AS evening_sales,
      SUM(IF(sales.type = 'night', sales.amount, 0.0)) AS night_sales
    FROM sales
      JOIN articles ON sales.article_id = articles.id
    WHERE
      sales.date >= "2011-01-01 00:00:00"
      AND sales.date < "2011-02-01 00:00:00"
    GROUP BY sales.article_id
    

    如果还有其他类型,则必须在那里添加更多列,或者通过将其添加到 SELECT 子句来简单地总结其他类型:

    SUM(
      IF(sales.type IS NULL OR sales.type NOT IN ('morning', 'evening', 'night'), 
        sales.amount, 0.0
      )
    ) AS other_sales
    

    以上与 MySQL 兼容。要在 Postgres 中使用它,我认为您必须将 IF 表达式更改为 CASE expressions,它应该看起来像这样(未经测试):

    SELECT
      articles.name AS article_name,
      SUM(CASE WHEN sales.type = 'morning' THEN sales.amount ELSE 0.0 END) AS morning_sales,
      SUM(CASE WHEN sales.type = 'evening' THEN sales.amount ELSE 0.0 END) AS evening_sales,
      SUM(CASE WHEN sales.type = 'night' THEN sales.amount ELSE 0.0 END) AS night_sales
    FROM sales
      JOIN articles ON sales.article_id = articles.id
    WHERE
      sales.date >= "2011-01-01 00:00:00"
      AND sales.date < "2011-02-01 00:00:00"
    GROUP BY sales.article_id
    

    【讨论】:

      【解决方案2】:

      两个选项。

      选项 1. 带有用于聚合的计算列的单个连接

      select article_name  = a.article_name ,
             morning_sales = sum( case when sales.type = 'morning' then sales.amount end ) ,
             evening_sales = sum( case when sales.type = 'evening' then sales.amount end ) ,
             night_sales   = sum( case when sales.type = 'night'   then sales.amount end ) ,
             other_sales   = sum( case when sales.type in ( 'morning','evening','night') then null else sales.amount end ) ,
             total_sales   = sum( sales.amount )
      from articles a
      join sales    s on s.articles_id = a.articles_id
      where sales.date between @dtFrom and @dtThru
      group by a.article_name
      

      选项 2. 多个左连接

      select article_name = a.article_name ,
             morning_sales = sum( morning.amount ) ,
             evening_sales = sum( evening.amount ) ,
             night_sales   = sum( night.amount   ) ,
             other_sales   = sum( other.amount   ) ,
             total_sales   = sum( total.amount   )
      from      articles a
      left join sales    morning on morning.articles_id = a.articles_id
                                and morning.type        = 'morning'
                                and morning.date between @dtFrom and @dtThru
      left join sales    evening on evening.articles_id = a.articles_id
                                and evening.type        = 'evening'
                                and evening.date between @dtFrom and @dtThru
      left join sales    night   on night.articles_id   = a.articles_id
                                and night.type          = 'evening'
                                and night.date between @dtFrom and @dtThru
      left join sales    other   on other.articles_id = a.articles_id
                                and (    other.type is null
                                      OR other.type not in ('morning','evening','night')
                                    )
                                and other.date between @dtFrom and @dtThru
      left join sales    total   on total.articles_id = a.articles_id
                                and total.date between @dtFrom and @dtThru
      group by a.article_name
      

      【讨论】:

      • 相当不错;我喜欢选项 2 背后的想法作为自联接的替代方案。但选项 2 有 3 个问题。 (1) 你只选择了一个值,文章名称!您还需要从自联接中选择值。 (2) 根据查询的使用方式,可能需要列名。所以,查询应该以Select a.article_name, Sum(morning.amount)MorningAmount, Sum(evening.amount)EveningAmount,(等等)开头。 (3) 请注意,sum(Total.amount) 将始终等于sum(a.Amount),因此您的自联接数比您需要的多;完全忽略 total 。
      • 我的查询与 OP 的示例相匹配。但是,如果您需要使用分组列或聚合函数以外的列来装饰聚合结果集,则复杂性会上升。有些人只是将它们按列表添加到分组中,但根据我的经验,这会导致......有趣的结果......当数据(一如既往)不如应有的干净时。
      • 非常感谢您的回答。我要选择约翰的那个只是因为它来得早了一点。如果可以,我会选择两者。无论如何+1!问候!
      • SELECT 中的“赋值”看起来很奇怪,并且不适用于 PostgreSQL。那应该做什么?哪种 SQL 方言允许在 SELECT 子句中定义变量?
      • select &lt;column_name&gt; = &lt;expression&gt; ... 语法是经典的 SQL,(至少)可以追溯到 DB2 第 1 版第 2 版。许多 SQL 实现都支持它,但不是全部(著名的 Oracle)。它与select &lt;expression&gt; as &lt;column_name&gt; 完全相同。您在select 语句中的何处看到变量定义?确实有变量引用@dtFrom/@dtThru),但没有变量定义。
      猜你喜欢
      • 1970-01-01
      • 2021-02-04
      • 1970-01-01
      • 2017-06-14
      • 1970-01-01
      • 1970-01-01
      • 2019-01-21
      • 2022-01-03
      • 1970-01-01
      相关资源
      最近更新 更多