执行两个GROUP BY的SQL查询?

时间:2011-05-09 21:44:30

标签: sql ruby-on-rails postgresql

我无法获取需要生成的报告的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应用程序。

2 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分: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