在没有 GROUP BY 的情况下使用 HAVING 无法按预期工作

Use of HAVING without GROUP BY not working as expected

我开始学习 SQL 服务器,在 msdn 中找到的文档中这样说

HAVING is typically used with a GROUP BY clause. When GROUP BY is not used, there is an implicit single, aggregated group.

这让我想到我们可以在没有 groupBy 子句的情况下使用 having,但是当我尝试进行查询时我无法使用它。

我有一个table这样的

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

错误:

文档正确;也就是说,你可以 运行 这个语句:

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

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

我能想到的用例不多,你想在没有分组依据的情况下使用 having,但肯定可以如上所示完成。

注意:如果您想要工资大于 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 岁以上和 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.

文档仅表示虽然通常您会有一个带有 group by 语句的 having 语句,但可以排除 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 

难道select与having从句无关吗?

是也不是。 Having 是对聚合数据的过滤器。 Select 说什么 columns/information 带回来。因此你必须问 "what would the result look like?" 即鉴于我们必须有效地应用 group by 1 来使用 having 语句,SQL 应该如何解释 select * ?由于您的 table 只有一列,因此这将转换为 select wage;但是我们有 5 行,所以 wage 有 5 个不同的值,结果中只有 1 行显示这个。

我想你可以说 "I want to return all rows if their sum is greater than 5; otherwise I don't want to return any rows"。如果您的要求可以通过多种方式实现;其中之一是:

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

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

考虑 having 的另一种方式是将 where 语句应用于子查询。 IE。您的原始声明实际上是:

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

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

如下例

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

在你的情况下,你的 table 没有身份列,它应该如下所示

Alter _abc
Add Id_new Int Identity(1, 1)
Go
没有 GROUP BY 子句的

HAVING 是完全有效的,但您需要了解以下内容:

  • 结果将包含零行或一行
    • 即使 WHERE 条件匹配零行
    • ,隐式 GROUP BY 也将 return 恰好一行
    • 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