我已经写了这个查询
SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME
FROM employees
WHERE SALARY > (SELECT AVG(SALARY)
FROM employees)
Run Code Online (Sandbox Code Playgroud)
我有点困惑为什么我必须为它创建一个子查询以及为什么我不能像这样编写查询:
SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME
FROM employees
WHERE SALARY > AVG(SALARY)
Run Code Online (Sandbox Code Playgroud)
SQL 是一种基于集合的语言。如果您仅聚合一组中的一列,则必须聚合整个组。您不能既聚合一个集合又不聚合一个集合。此外,一条SELECT语句仅定义一个集合。因此,您无法在同一 SELECT 语句中将聚合集中的值与非聚合集(两个集)中的值进行比较。
您的第一个语句之所以有效,是因为您有两个 SELECT 语句,每个语句都定义了一个不同的集合。一组通过该函数进行聚合avg(),另一组保持非聚合状态。
它也有效,因为聚合集是标量(它具有单个值)。它可以将非聚合集中的每一行与单值聚合集中保存的标量值进行比较。如果聚合集的定义方式不是标量,那么就会抛出错误,并且您必须通过子句内部的ON子句或通过相关子查询在两个集合之间建立关系。JOINFROM
另一个原因是操作顺序。通过WHERE子句进行过滤首先发生在 SQL 的执行过程中。在过滤您的数据时,尚未发生其他操作。GROUP BY/聚合发生在 SQL 执行的后期。因此,您尝试将 SQL 中两个截然不同的步骤的结果进行比较。
操作顺序是其存在的原因HAVING。它与WHERE聚合非常相似,但在聚合之后起作用。但这对您在此 SQL 中尝试执行的操作没有帮助,因为您再次尝试将非聚合集中的值与聚合集中的值进行比较,而这根本无法在单个 SELECT 语句。
可能值得注意的是,您可以使用大多数 RDBMS 都支持的“窗口函数”(也称为“有序分析函数”或“分析函数”)在非聚合集合内进行聚合(可以这么说)。
例如:
SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME, SALARY, AVG(SALARY) OVER () as avg_salary
FROM employees;
Run Code Online (Sandbox Code Playgroud)
这仍然会为 table 中的每一行吐出一个非聚合行employees。它将有一个第四列,其中每行包含所有员工的平均工资。每行都将包含相同的值。
+-------------+------------+------------+--------+------------+
| EMPLOYEE_ID | FIRST_NAME | LAST_NAME | SALARY | avg_salary |
+-------------+------------+------------+--------+------------+
| 1 | bob | mcbob | 100 | 210 |
| 2 | sue | o'susan | 230 | 210 |
| 3 | venkat | van venkat | 300 | 210 |
+-------------+------------+------------+--------+------------+
Run Code Online (Sandbox Code Playgroud)
话虽如此,由于操作顺序的原因,您无法比较一个WHERE或一个子句内的窗口函数的结果。HAVING窗口函数逻辑在 SQL 执行中的几乎所有其他步骤之后运行。您只会收到一条错误消息,表明这是不允许的。因此,您将再次需要两个 SELECT 语句:
SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME
FROM
(
SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME, SALARY, AVG(SALARY) OVER () as avg_salary
FROM employees;
) dt
WHERE SALARY > avg_salary;
Run Code Online (Sandbox Code Playgroud)
最后,市场上有两种 RDBMS 具有QUALIFY类似于WHEREorHAVING子句的子句(Snowflake 和 Teradata),允许窗口函数成为过滤器的一部分。如果您使用这两个平台之一,那么您可以将其编写为单个 SELECT 语句:
SELECT EMPLOYEE_ID, FIRST_NAME, LAST_NAME
FROM employees
QUALIFY SALARY > AVG(SALARY) OVER ();
Run Code Online (Sandbox Code Playgroud)
就像WHERE执行开始时的操作一样,HAVING接近执行结束时的操作,QUALIFY甚至比执行结束更晚的操作(就在之前ORDER BY)。奇怪的是,这正是您最初想要的,并且一些 RDBMS 预料到了您的需求。我希望更多的 RDBMS 在未来版本中采用 QUALIFY 子句,因为它非常方便。