课程
结构化查询语言(SQL)是世界上最重要的编程语言之一,广泛应用于数据科学家和数据工程师的日常工作中。
随着越来越多的人依赖数据驱动业务,对 SQL 专业技能的需求这些年只增不减。好在这门语言易学且成熟,拥有庞大的用户社区,可以在您遇到问题时提供支持。
本文将介绍 SQL 语言中的一个特定子句——QUALIFY。阅读完后,您将了解:
- QUALIFY 的基本概念与语法
- 何时使用 QUALIFY 子句
- 它与其他过滤方法有何不同。
SQL QUALIFY 子句的基本概念与语法
QUALIFY 是用于筛选窗口函数结果的子句。因此,要成功使用 QUALIFY,SELECT 列表或 QUALIFY 子句中必须至少包含一个窗口函数——只有布尔表达式计算为 TRUE 的行才会返回。
提示:查看SQL 窗口函数速查表。
如果没有 QUALIFY,要对窗口函数的结果进行过滤就需要嵌套。SQL 开发者通常建议尽量避免嵌套查询,因为这会降低代码的可读性,并增加调试与协作的复杂度。
换言之,QUALIFY 子句的主要目的就是简化对窗口函数结果进行过滤的查询;这意味着 QUALIFY 会在窗口函数计算完成之后再进行求值。
包含 QUALIFY 子句的查询通常按以下顺序执行:
- FROM
- WHERE
- GROUP BY 和聚合
- HAVING
- WINDOW
- QUALIFY
- DISTINCT
- ORDER BY
- LIMIT
QUALIFY 语句的一般语法如下:
QUALIFY <predicate>
其通用形式类似于:
SELECT <column_list>
FROM <data_source>
[GROUP BY ...]
[HAVING ...]
QUALIFY <predicate>
[ ... ]
注:<predicate> 是在聚合与窗口函数计算完成后用于过滤结果的表达式。
何时使用 SQL QUALIFY
QUALIFY 子句与 HAVING 子句有些相似,因为它也能避免为实现过滤而编写子查询。比如,您可以用 QUALIFY 来筛选分析函数(基于一组行计算聚合值的函数)的结果。
为更清楚地说明何时使用 QUALIFY,来看一个简短情境:
假设您是一名数据科学家,某位相关方希望获取每位客户的最近一次登录信息。您的初始查询可能是这样的:
SELECT
user_id,
ip,
country_code,
os,
RANK() over (
PARTITION BY user_id ORDER BY log_datetime DESC
) as previous_logins
FROM login_logs
WHERE TRUE
这是个不错的开始,但它并不能满足需求方的要求,因为它会返回所有登录记录,而不只是最近一次。要返回最后一次登录,我们需要加上过滤条件。
SQL 新手可能会误用 WHERE 子句来过滤,如下所示:
SELECT
user_id,
ip,
country_code,
os,
RANK() over (
PARTITION BY user_id ORDER BY log_datetime DESC
) AS previous_logins
FROM login_logs
WHERE TRUE
AND last_login = 1
遗憾的是,这段代码无法执行;即便尝试使用 GROUP BY 和 HAVING 也一样。
原因在于上一节提到的执行顺序。
WHERE、GROUP BY 和 HAVING 都在窗口函数之前求值;也就是说,它们无法过滤窗口函数的结果,因为在这三个子句运行时,窗口函数还未被计算,自然“无从得知”。
让过滤发生在窗口函数之后的一种方法是使用公用表表达式(CTE)。
代码可以这样写:
WITH
logins AS (
SELECT
user_id,
ip,
country_code,
os,
RANK() OVER (
PARTITION BY user_id ORDER BY log_datetime DESC
) AS previous_logins
FROM login_logs
WHERE TRUE
)
SELECT user_id, ip, country_code, os
FROM logins
WHERE previous_logins = 1
从技术上讲,这段代码是正确的——它能顺利执行,并返回需求方想要的结果。
不过,我们引入了两个查询,还多了几行代码;如果这种模式经常重复,就会显得累赘。
解决该问题的最佳做法是使用 QUALIFY。
代码如下:
-- Starter code from @Jiho Choi on StackOverflow
SELECT
user_id,
ip,
country_code,
os,
RANK() over (
PARTITION BY user_id ORDER BY log_datetime DESC
) as previous_logins
FROM login_logs
WHERE TRUE
QUALIFY previous_logins = 1
这个方案之所以可行,是因为在 SQL 的执行顺序中,QUALIFY 会在窗口函数之后求值,这意味着它“知道”窗口函数的存在,因此可以在同一条查询中对其进行过滤。
您可能会好奇,除了减少代码行数,QUALIFY 还有哪些好处?答案是并没有明显的性能优势。
我们使用的 CTE 方案与 QUALIFY 方案的执行时间相近,因此很难说 QUALIFY 带来了显著的性能提升。
其主要优势在于使用体验更好:代码更少,更易读。
将 SQL QUALIFY 与其他过滤方法进行比较
此时,您已经知道 QUALIFY 子句是 SQL 中可用的另一种过滤方法,也清楚何时该使用它。
那么,QUALIFY 与其他过滤方法有何不同?
让我们回顾各类过滤方法。
WHERE 子句
WHERE 用于筛选表中的记录,即对行进行操作。我们在需要提取满足特定条件的记录时使用它。比如,想按照某人的年龄过滤数据集。代码如下:
SELECT name, gender, height, weight, age
FROM gym_members_info
WHERE age < 23;
关于 WHERE 子句的要点如下:
- 用于基于特定条件过滤记录。
- 在行上执行操作。
- 可以不与 GROUP BY 搭配使用。
- 可用于 SELECT、UPDATE、DELETE 语句。
下面与 HAVING 子句对比。
HAVING 子句
HAVING 用于基于特定条件,筛选由 GROUP BY 子句定义的各组记录。因此,WHERE 与 HAVING 最显著的区别在于,HAVING 需要存在 GROUP BY 子句才能成功求值。
其主要好处是,您可以将 HAVING 应用于聚合后的部分分组,而在 WHERE 中无法做到这一点。
例如:
SELECT gender, AVG(height)
FROM gym_members_info
GROUP BY gender
HAVING AVG(height) > 170
上述代码将返回平均身高大于 170 厘米的性别分组;换句话说,HAVING 执行的是列级操作。
另一个需要注意的是,与 WHERE 不同,HAVING 只能用于 SELECT 语句。
QUALIFY 子句
QUALIFY 与 HAVING 类似,但它用于对窗口函数的结果进行过滤;而 HAVING 则用于对聚合函数和 GROUP BY 子句的结果进行过滤——进一步了解如何使用 GROUP BY。
如果我们想知道各年龄组中身高最高者的信息,可以用窗口函数进行计算,再用 QUALIFY 子句过滤结果,只保留每个年龄组中身高最高的人。
代码如下:
SELECT name, gender, height, weight, age
RANK() over (
PARTITION BY age ORDER BY height DESC
) as ranked_ages
FROM gym_members_info
WHERE TRUE
QUALIFY ranked_ages = 1
本质上,QUALIFY 子句指定了一个条件表达式,用于筛选已按照用户指定条件计算完成的有序分析函数的输出。
结论与进阶学习
SQL 在典型的数据科学工作流程中扮演着重要角色;掌握诸如 QUALIFY 子句等有用操作非常有帮助,因为它能让您写出更整洁的代码,便于他人与您协作。
如果您想精进 SQL 技能,推荐查看以下资源:
