我正在使用 BigQuery 并遇到了 QUALIFY 子句,我知道它可用于基于窗口函数过滤结果。但是,我注意到 QUALIFY 子句如何访问列方面存在一些细微差别,特别是取决于窗口定义的位置。
有人可以澄清 QUALIFY 子句如何与 FROM 子句和 SELECT 列表别名中的列相关吗?
观察力很棒!在 BigQuery 的 SQL 中,确实有两种类型的列:
QUALIFY 子句的行为根据窗口函数所在的位置而有所不同。让我们使用以下数据集深入研究示例以进行澄清:
WITH a AS (
SELECT
*
FROM UNNEST([STRUCT(1 AS val, 'in' AS position),
(2, 'out'),
(3, 'above'),
(1, 'in')
])
)
当您在 SELECT 列表中定义窗口函数时,QUALIFY 只能访问通过 FROM 子句可用的列。
示例 1.1:这是一个有效的查询,因为 QUALIFY 子句基于 rn 列进行过滤,该列源自 FROM 子句中的位置列。
SELECT
CASE WHEN position = 'above' THEN 'in'
ELSE position
END AS mapped_position,
val,
ROW_NUMBER() OVER w1 as rn,
FROM a
QUALIFY rn = 1
WINDOW w1 AS (PARTITION BY position)
示例 1.2:下面的查询无效。当 QUALIFY 尝试根据 rn 列进行过滤时,窗口函数使用mapped_position,它是 SELECT 列表中的别名。因此,此时它无法访问mapped_position。
SELECT
CASE WHEN position = 'above' THEN 'in'
ELSE position
END AS mapped_position,
val,
ROW_NUMBER() OVER w1 as rn,
FROM a
QUALIFY rn = 1
WINDOW w1 AS (PARTITION BY mapped_position)
当您直接在 QUALIFY 子句中定义窗口函数时,它可以访问 FROM 子句中的列和作为 SELECT 列表别名的列。
示例 2.1:这是一个有效的查询。 QUALIFY 基于窗口函数进行过滤,该窗口函数按 FROM 子句中的
position
列进行分区。
SELECT
CASE WHEN position = 'above' THEN 'in'
ELSE position
END AS mapped_position,
val
FROM a
QUALIFY ROW_NUMBER() OVER (PARTITION BY position) = 1
例2.2:这个查询是有效的,窗口函数在QUALIFY中定义,通过mapped_position进行分区,这是SELECT列表中的别名。所以,此时它可以访问
mapped_position
。
SELECT
CASE WHEN position = 'above' THEN 'in'
ELSE position
END AS mapped_position,
val
FROM a
QUALIFY ROW_NUMBER() OVER (PARTITION BY mapped_position) = 1
总而言之,窗口定义的位置会影响 QUALIFY 子句可以访问的列。在 SELECT 列表内定义窗口函数时,QUALIFY 只能访问 FROM 子句中的列。但是,当直接在 QUALIFY 中定义窗口函数时,它可以访问 FROM 子句中的列以及 SELECT 子句中定义的别名。
知道了,猜猜这个 SQL 会返回什么结果?
WITH a AS (
SELECT
*
FROM UNNEST(
[STRUCT(1 AS val, 'in' AS position),
(2, 'out'),
(3, 'above'),
(1, 'in')
])
)
SELECT
CASE WHEN position = 'above' THEN 'in'
ELSE position
END AS position,
val
FROM a
QUALIFY ROW_NUMBER() OVER (PARTITION BY position) = 1