在雪花中使用嵌套窗口函数

Posted

技术标签:

【中文标题】在雪花中使用嵌套窗口函数【英文标题】:Using nested window function in Snowflake 【发布时间】:2021-04-08 21:34:06 【问题描述】:

我已经看到很多关于这个一般错误的问题,但我不明白为什么会出现它,可能是因为嵌套的窗口函数......


通过以下查询,我得到了Col_CCol_D、...以及我尝试过的几乎所有内容的错误

SQL 编译错误:[eachColumn] 不是有效的按表达式分组

SELECT
    Col_A,
    Col_B,
    FIRST_VALUE(Col_C) IGNORE NULLS OVER (PARTITION BY Col_A, Col_B
                                    ORDER BY Col_TimeStamp ASC 
                                    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),
    MAX(Col_D)                      OVER (PARTITION BY Col_A, Col_B
                                    ORDER BY Col_TimeStamp ASC
                                    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),
    FIRST_VALUE(CASE WHEN Col_T = 'testvalue'
                THEN LAST_VALUE(Col_E) IGNORE NULLS OVER (PARTITION BY Col_A, Col_B
                                                    ORDER BY Col_TimeStamp DESC 
                                                    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
                ELSE NULL END) IGNORE NULLS 
                                    OVER (PARTITION BY Col_A, Col_B
                                    ORDER BY Col_TimeStamp ASC
                                    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM mytable

那么,有没有在 Snowflake 中使用嵌套窗口函数的方法(使用 case when ...),如果有,我怎么/我做错了什么?

【问题讨论】:

从选择中选择? @astentx :是的,是这个问题吗? 我只见过一个数据库 (Exasol) 可以在同一级别上重用本地计算(我的同事告诉我 Teradata 也可以做到这一点)。在大多数 DBMS 中,这需要使用 CTE(WITH 子句)或FROM 中的子查询逐步重写查询,其中分析函数不使用内部分析函数。因此,在每个新的selectstatement 中,您都使用上一级分析函数的结果 您应该提供样本数据、期望的结果以及您想要做什么的清晰解释。嵌套的窗口函数对编译器没有意义。它们对我来说也没有意义。 这对我来说非常有意义(这是我在雪花中写的小版本)如果有一个 GROUP BY,那么上下文将是模糊的,此时第二层窗口函数需要上下文来了解“什么是什么”。但是这里产生了相同数量的行。 AKA 这只是 SF 允许的完美可爱的函数式编程。 【参考方案1】:

所以解构你的逻辑以表明它是导致问题的第二个 FIRST_VALUE

WITH data(Col_A,Col_B,Col_c,col_d, Col_TimeStamp, col_t,col_e) AS (
    SELECT * FROM VALUES
        (1,1,1,1,1,'testvalue',10),
        (1,1,2,3,2,'value',11)
)
SELECT
    Col_A,
    Col_B,
    FIRST_VALUE(Col_C) IGNORE NULLS OVER (PARTITION BY Col_A, Col_B 
                                    ORDER BY Col_TimeStamp ASC 
                                    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as first_c,
    MAX(Col_D)                      OVER (PARTITION BY Col_A, Col_B
                                    ORDER BY Col_TimeStamp ASC
                                    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),
    LAST_VALUE(Col_E) IGNORE NULLS OVER (PARTITION BY Col_A, Col_B
                                    ORDER BY Col_TimeStamp DESC 
                                    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as last_e,   
    IFF(Col_T = 'testvalue', last_e, NULL) as if_test_last_e
    /*,FIRST_VALUE(if_test_last_e) IGNORE NULLS OVER (PARTITION BY Col_A, Col_B 
                                    ORDER BY Col_TimeStamp ASC 
                                    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as the_problem*/
FROM data
ORDER BY Col_A,Col_B, col_timestamp
;

如果我们取消注释 the_problem,我们就有了。与 PostgreSQL(我的背景)相比,重用这么多先前的结果/步骤是一种天赋,所以在这里我只是打破了另一个 SELECT 层。

WITH data(Col_A,Col_B,Col_c,col_d, Col_TimeStamp, col_t,col_e) AS (
    SELECT * FROM VALUES
        (1,1,1,1,1,'testvalue',10),
        (1,1,2,3,2,'value',11)
)
SELECT *,
    FIRST_VALUE(if_test_last_e) IGNORE NULLS OVER (PARTITION BY Col_A, Col_B 
                                    ORDER BY Col_TimeStamp ASC 
                                    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as not_a_problem
FROM (
    SELECT
        Col_A,
        Col_B,
        FIRST_VALUE(Col_C) IGNORE NULLS OVER (PARTITION BY Col_A, Col_B 
                                        ORDER BY Col_TimeStamp ASC 
                                        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as first_c,
        MAX(Col_D)                      OVER (PARTITION BY Col_A, Col_B
                                        ORDER BY Col_TimeStamp ASC
                                        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),
        LAST_VALUE(Col_E) IGNORE NULLS OVER (PARTITION BY Col_A, Col_B
                                        ORDER BY Col_TimeStamp DESC 
                                        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as last_e,   
        IFF(Col_T = 'testvalue', last_e, NULL) as if_test_last_e
        ,Col_TimeStamp
    FROM data
)
ORDER BY Col_A,Col_B, Col_TimeStamp

然后一切正常。如果您 LAG 然后 IFF/FIRST_VALUE 然后 LAG 第二个结果,也会发生这种情况。

【讨论】:

感谢您的意见,但我选择了横向参考,因为 CTE 看起来有点笨拙且不那么可读。 @R3uK CTE 只是为了提供“测试数据”,它不是实际解决方案的一部分,但我喜欢提供可以复制到 Snowflake 并按原样运行的解决方案,所以你可以和他们一起玩,看看它是如何工作的。 “横向参考”也只是您编码的技术名称,我的代码和 Lukasz 代码都在做巫婆,指的是 SELECT 部分中的输出列,而在同一个 SELECT 部分,这不是有效的 ANSI SQL,但对保持代码清洁非常有帮助。【参考方案2】:

“我已经看到很多关于这个一般错误的问题,但我不明白我为什么会有它,可能是因为嵌套的窗口函数......”

Snowflake 支持在同一级别重用表达式(有时称为"lateral column alias reference")

写的很好:

SELECT 1+1 AS col1,
       col1 *2 AS col2,
       CASE WHEN col1 > col2 THEN 'Y' ELSE 'NO' AS col3
       ...

在标准 SQL 中,您要么必须使用多级查询 (cte),要么使用 LATERAL JOIN。相关:PostgreSQL: using a calculated column in the same query


不幸的是,相同的语法不适用于分析函数(我现在知道任何支持它的 RDMBS):

SELECT ROW_NUMBER() OVER(PARTITION BY ... ORDER BY ...) AS rn
      ,MAX(rn) OVER(PARTITION BY <different than prev) AS m
FROM tab;

在 SQL 标准 2016 中有一个可选功能:T619 嵌套窗口函数

这里有一篇文章如何嵌套分析函数查询:Nested window functions in SQL。

这意味着当前嵌套窗口函数的方法是使用派生表/cte:

WITH cte AS (
    SELECT ROW_NUMBER() OVER(PARTITION BY ... ORDER BY ...) AS rn
           ,*
    FROM tab
)
SELECT  *, MAX(rn) OVER(PARTITION BY <different than prev) AS m
FROM cte

【讨论】:

而 CTE 只是一个子选择,也就是说,在某些时候,您必须明确说明您使用的数据范围,以帮助编译器了解您的意思。 我就是这样走的,因为 CTE 看起来很笨拙且不那么易读,我通过横向引用设法满足了我的需要,并且仍然非常易读和高效;)

以上是关于在雪花中使用嵌套窗口函数的主要内容,如果未能解决你的问题,请参考以下文章

Oracle SQL 模拟嵌套窗口函数

雪花窗函数 last_value 和 max

C语言中怎么使用API函数 求使用方法

WPF 使用SetParent嵌套窗口

MFC中嵌套QT窗口,如果让qt窗口跟随MFC的窗口移动

WPF 使用SetParent嵌套窗口