使用不带GROUP BY的HAVING不能按预期工作

时间:2018-11-10 08:33:10

标签: sql sql-server group-by having

在这样的msdn状态下找到的文档中,我开始学习SQL Server

  

HAVING通常与GROUP BY子句一起使用。如果不使用GROUP BY,则存在一个隐式的单个聚合组。

这使我认为我们可以不使用groupBy子句而使用having,但是当我尝试进行查询时,我将无法使用它。

我有一张这样的桌子

CREATE TABLE [dbo].[_abc]
(
    [wage] [int] NULL
) ON [PRIMARY]
GO

INSERT INTO [dbo].[_abc] (wage)
VALUES (4), (8), (15), (30), (50) 
GO

现在,当我运行此查询时,我得到一个错误

select * 
from [dbo].[_abc]
having sum(wage) > 5

错误:

enter image description here

3 个答案:

答案 0 :(得分:4)

文档正确无误;也就是说,您可以运行以下语句:

select sum(wage) sum_of_all_wages
, count(1) count_of_all_records
from [dbo].[_abc] 
having sum(wage) > 5

您的语句不起作用的原因是由于select *,这意味着选择每列的值。如果没有group by,则所有记录都将汇总;也就是说,您在结果集中只能获得1条记录,该记录必须代表每条记录。因此,您只能*包括通过将聚合函数应用于列而提供的值;不是列本身。 *当然,您也可以提供常量,因此select 'x' constant, count(1) cnt from myTable可以使用。

在没有分组依据的情况下,我能想到的用例并不多,但可以肯定地做到了,如上所示。

注意:如果您希望所有工资都高于5的行,则可以使用where子句:

select * 
from [dbo].[_abc] 
where wage > 5

同样,如果您希望所有工资的总和大于5,则可以这样做

select sum(wage) sum_of_wage_over_5 
from [dbo].[_abc] 
where wage > 5

或者如果您想比较5岁以下的工资总和:

select case when wage > 5 then 1 else 0 end wage_over_five
, sum(wage) sum_of_wage
from [dbo].[_abc] 
group by case when wage > 5 then 1 else 0 end 

请参见runnable examples here


根据评论进行更新:

是否需要having才能使用聚合函数?

不。您可以运行select sum(wage) from [dbo].[_abc]。当使用不带group by子句的聚合函数时,就好像您是按常量分组一样。即select sum(wage) from [dbo].[_abc] group by 1

该文档仅意味着,尽管通常您会有一个having语句和一个group by语句,但是可以排除group by /在这种情况下可以排除having语句,像select语句一样,将您的查询视为已指定group by 1

有什么意义?

很难想到很多好的用例,因为您只返回了一行,而having语句是对此的过滤器。

一个用例是您编写代码以监视某些软件的许可证;如果您的用户数少于每用户许可数,那么一切都很好/您不希望看到结果,因为您不在乎。如果您有更多的用户,您想了解它。例如

declare @totalUserLicenses int = 100
select count(1) NumberOfActiveUsers
, @totalUserLicenses NumberOfLicenses
, count(1) - @totalUserLicenses NumberOfAdditionalLicensesToPurchase
from [dbo].[Users]
where enabled = 1
having count(1) > @totalUserLicenses 

选择不是与hading子句无关吗?

是,不是。拥有过滤汇总数据的功能。选择说什么带回的列/信息。因此,您必须问“结果会是什么样?”即,鉴于我们必须有效地应用group by 1来利用having语句,SQL应该如何解释select *?由于您的表只有一列,因此它将转换为select wage;但我们有5行,因此wage的5个不同值,结果中只有1行显示此内容。

我猜你可能会说:“如果它们的总和大于5,我想返回所有行;否则,我不想返回任何行”。您的要求是否可以通过多种方式实现?其中之一是:

select *
from [dbo].[_abc] 
where exists 
(
    select 1 
    from [dbo].[_abc] 
    having sum(wage) > 5
) 

但是,我们必须编写代码来满足要求,而不是期望代码理解我们的意图。

思考having的另一种方法是作为应用于子查询的where语句。即您的原始声明有效地显示为:

select wage
from
(
    select sum(wage) sum_of_wage
    from [dbo].[_abc]
    group by 1
) singleRowResult
where sum_of_wage > 5

该操作将无法运行,因为wage无法用于外部查询;仅返回sum_of_wage

答案 1 :(得分:1)

HAVING(无GROUP BY子句是完全有效的,但这是您需要了解的内容:

  • 结果将包含零行或一行
    • 即使GROUP BY条件匹配零行,隐式WHERE也将只返回一行
    • HAVING将根据条件保留或消除该单行
  • SELECT子句中的任何列都需要包装在聚合函数中
  • 您还可以指定一个表达式,只要它在功能上不依赖于列

这意味着您可以这样做:

SELECT SUM(wage)
FROM employees
HAVING SUM(wage) > 100
-- One row containing the sum if the sum is greater than 5
-- Zero rows otherwise

甚至是这样:

SELECT 1
FROM employees
HAVING SUM(wage) > 100
-- One row containing "1" if the sum is greater than 5
-- Zero rows otherwise

当您有兴趣检查是否找到聚合的匹配项时,通常使用此构造:

SELECT *
FROM departments
WHERE EXISTS (
    SELECT 1
    FROM employees
    WHERE employees.department = departments.department
    HAVING SUM(wage) > 100
)
-- all departments whose employees earn more than 100 in total

答案 2 :(得分:0)

在SQL中,您不能直接返回聚合函数列。您需要对非聚集字段进行分组

如下例所示

 USE AdventureWorks2012 ;  
GO  
SELECT SalesOrderID, SUM(LineTotal) AS SubTotal  
FROM Sales.SalesOrderDetail  
GROUP BY SalesOrderID  
HAVING SUM(LineTotal) > 100000.00  
ORDER BY SalesOrderID ;  

在您的情况下,您的表没有标识列,它应该如下所示

Alter _abc
Add Id_new Int Identity(1, 1)
Go