为什么MySQL允许HAVING使用SELECT别名?


14

据我所知,在SQL中,逻辑查询处理顺序(即概念解释顺序)以以下方式从FROM开始:

  1. 哪里
  2. 通过...分组
  3. 拥有
  4. 选择
  5. 订购

在此列表之后,很容易看出为什么在WHERE子句中没有SELECT别名,因为尚未创建别名。T-SQL(SQL Server)严格遵循此原则,在传递SELECT之前,您不能使用SELECT别名。

但是在MySQL中,即使应该(在逻辑上)在SELECT子句之前对其进行处理,也可以在HAVING子句中使用SELECT别名。这怎么可能?

举个例子:

SELECT YEAR(orderdate), COUNT(*) as Amount
FROM Sales.Orders
GROUP BY YEAR(orderdate) 
HAVING Amount>1;

该语句在T-SQL中无效(因为HAVING引用了SELECT别名Amount)...

Msg 207, Level 16, State 1, Line 5
Invalid column name 'Amount'.

...但是在MySQL中工作正常。

基于此,我想知道:

  • MySQL是否在SQL规则中采取捷径来帮助用户?也许使用某种预分析?
  • 还是MySQL使用的概念解释顺序与我遵循的所有RDBMS遵循的顺序不同?

1
我的猜测是,这是您的第二点。
a_horse_with_no_name

3
好吧,我想除非它们支持排名功能,否则不会引起任何歧义或混淆。然后SELECT C, ROW_NUMBER() OVER (ORDER BY X) AS RN FROM T GROUP BY C HAVING RN = 1将问题的,因为ROW_NUMBER运行HAVING
马丁·史密斯

我不确定MySQL支持哪些排序功能。如果您想要行号,则必须以这种方式创建它:SELECT @rownum:=@rownum + 1 as row ...。也许它们之所以支持SELECT别名的原因仅仅是因为它们可以,因为它们不支持使之成为不可能的事情……谁知道呢?:)
Ohlin

正如@MartinSmith解释的那样,只要没有窗口/排序功能,就可以互换HAVINGand SELECT子句的逻辑执行顺序。因此,这样做没有任何歧义,并且当中存在怪异的表达式时,可以简化代码的外观SELECT
ypercubeᵀᴹ

希望这是有点切合主题说我回答的问题在这里是享受更快的结果(用distincts)......与Alias in the Having不管相同的Explain输出。因此,优化程序正在发生一些变化。
德鲁

Answers:


13

好吧,如果您有这样的问题,最好的信息来源是MySQL文档。现在到了重点。这是GROUP BY默认情况下启用的MySql扩展的行为。

MySQL对GROUP BY的扩展
MySQL扩展了此行为,以允许在HAVING子句中为聚合列使用别名

如果您想要标准行为,则可以使用 sql_mode ONLY_FULL_GROUP_BY

SET [SESSION | GLOBAL] sql_mode = ONLY_FULL_GROUP_BY;

如果您尝试在ONLY_FULL_GROUP_BYsql_mode中执行上述查询,则会收到以下错误消息:

HAVING子句中使用非分组字段'Amount':SELECT YEAR(orderdate),COUNT(*)作为FROM FROM Orders GROUP BY YEAR(orderdate)的HAVING Amount> 1

这是SQLFiddle演示

因此,如何配置和使用MySQL实例取决于您。


您对文档绝对正确。我只是从未想过它可以像您在上面引用的那样清晰地写出来:)感谢您找到它……
Ohlin 2013年

该答案没有回答“ MySQL是在进行预分析还是MySQL使用了不同的概念解释?”。
Pacerier,2015年

2
@Pacerier MySQL正在“做预分析”,因为查询优化器会在选择认为最佳查询计划的同时考虑查询的所有方面。“不同的概念解释”的概念误解了以下事实:服务器可以以产生有效结果的任何方式自由实施概念模型。 ORDER BY例如,如果优化器发现可以从已经按所需顺序的索引开始按顺序读取行,那么实际上可能比理论上要早得多。
Michael-sqlbot

4

好问题。

我认为您应该运行这些查询

EXPLAIN SELECT YEAR(orderdate), COUNT(*) as Amount
FROM Sales.Orders
GROUP BY YEAR(orderdate) 
HAVING Amount>1;
SHOW WARNINGS;

并检查如何重写查询。我很确定查询优化器用COUNT(*)代替Amount

SELECT YEAR(orderdate), COUNT(*) as Amount
FROM Sales.Orders
GROUP BY YEAR(orderdate) 
HAVING COUNT(*)>1;

像这样

select 
 *
from 
 test
where 
 id = 5 - 3

查询优化器后,它是这样的。

select 
 test.id as 'id'
from 
 test
where 
 test.id = 2
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.