两个查询在它们应该等效时返回不同的结果?

Two queries returning different results when they should be equivalent?

我们的数据集从根本上将一组日期(从当前周到过去的几周)连接到一组部分,具体取决于这些部分是在该周或之前开始,还是在该周或之后结束。虽然最初这个查询给了我们预期的结果,但本周它开始给我们提供错误的结果。经过大量修改后,我们发现如果我们将查询更改为 LEFT JOIN,然后使用 WHERE 子句过滤查询,它会再次为我们提供正确的结果。

有什么区别?为什么一个有效而另一个无效? (奖励积分: 为什么原始查询在突然出现此错误之前工作了数周?)在 Redshift 上执行相同的内部联接会提供正确的结果,因此我们似乎是 Snowflake 的细微差别不明白。

原查询:

WITH week_list AS
(
    SELECT DATEADD(week, -4, DATE_TRUNC(week, CURRENT_DATE())) AS week_value

    UNION ALL

    SELECT DATEADD(week, 1, week_value)
    FROM week_list
    WHERE DATEADD(week, 1, week_value) < CURRENT_DATE()
),
active_sections_per_week AS
(
    SELECT 
        wl.week_value, s.id section_id
    FROM week_list wl
    JOIN schema.sections s ON wl.week_value >= DATE_TRUNC(week, s.starts_at)
                           AND wl.week_value <= DATE_TRUNC(week, s.ends_at)
)
SELECT 
    aspw.week_value,
    COUNT(DISTINCT aspw.section_id) count_sections
FROM 
    active_sections_per_week aspw
GROUP BY 1
ORDER BY 1 DESC

结果: 一行,日期为 2019-12-30(4 周前)。过去三周没有数据。

注意:如果您在第一个 CTE 中调整 DATEADD,则返回的第一个日期似乎总是加入成功。此行为仅在上周开始 - 以前,此查询提供了预期的行数(换句话说,第一个 DATEADD 中指定的周数)。

"Fixed"查询:

WITH week_list AS
(
    SELECT DATEADD(week, -4, DATE_TRUNC(week, CURRENT_DATE())) AS week_value

    UNION ALL

    SELECT DATEADD(week, 1, week_value)
    FROM week_list
    WHERE DATEADD(week, 1, week_value) < CURRENT_DATE()
),
active_sections_per_week AS
(
    SELECT wl.week_value, s.id section_id
    FROM week_list wl
    LEFT JOIN schema.sections s ON wl.week_value >= DATE_TRUNC(week, s.starts_at)
                                AND wl.week_value <= DATE_TRUNC(week, s.ends_at)
    WHERE s.id IS NOT NULL
)
SELECT aspw.week_value, COUNT(DISTINCT aspw.section_id) count_sections
FROM active_sections_per_week aspw
GROUP BY 1
ORDER BY 1 DESC

结果: returns 四行,日期为 2019-12-30 至 2020-01-20 的周,具有适当的部分计数。

这是 "week_list" 上的递归 CTE。 Redshift does not support recursive CTEs

Snowflake does support recursive CTEs,这可以解释行为上的差异。

如果没有基础数据,很难对此进行测试。如果您在 Redshift 中得到正确的结果,那么您很可能不需要或不需要递归 CTE。您可以修改它,使 "week_list" 不引用自身。

至于为什么它以前有效,可能 table 状态和递归 CTE 仅在特殊情况下有效。当 CURRENT_DATE() 前进时,它把它从那个特殊情况中取出来。此外,如果不在递归 CTE 中,s.id IS NOT NULL 的内部联接和左外部联接将 等效。

您可以在此处阅读有关递归 CTE 的更多信息:

https://docs.snowflake.net/manuals/user-guide/queries-cte.html#recursive-ctes-and-hierarchical-data

可以避免递归 CTE 如果 -4 周是此代码的常量:

WITH week_list AS (
    SELECT DATEADD(week, column1, DATE_TRUNC(week, CURRENT_DATE())) 
    FROM VALUES (-4),(-3),(-2),(-1),(0)
)

使用 JOIN 雪花会将过滤器移到执行堆栈中更高的位置,您可能发现了一个错误。与 LEFT JOIN 一样(即使它具有等效的 WHERE 子句,它也很可能避免激进的破坏优化。

我们昨晚发布了一个软件版本,但我们使用的是企业帐户,因此您可能已经在 2 天前升级了。此版本有许多影响我们的错误,我们将其回滚(对我们而言)

感谢您的所有反馈!好消息是你们都帮助我找到了一个我认为满意的解决方案。我还跟进了 Snowflake,以便他们可以调查此行为,看看这是否是我的用户错误,因为我不了解递归 CTE 的处理方式,或者它是否可能是最近版本中引入的错误。

这是我发现的:虽然递归适用于我应用它的用例(根据 CURRENT_DATE 生成日期列表),但它并不是绝对必要的。因为我们想要一个日期列表,所以我可以很容易地生成一个 table 并使用行号来执行 DATEADD 调整。

看起来像这样:

SELECT DATEADD(week, '-' || ROW_NUMBER() OVER (ORDER BY NULL), 
               DATEADD(week, 1, DATE_TRUNC(week, CURRENT_DATE()))) AS week_value
FROM table (generator(rowcount => 200))

这种方法的一大好处是我不再受限于 Snowflake 中的 MAX_RECURSIONS 设置(默认设置为 100)。由于我使用这些数据来创建 activity 随时间变化的图表,因此拥有 200 个值可以让我获得超过三年的历史记录,而不仅仅是不到 2 年的历史记录。如果我想扩展它,我也不必联系我的 Snowflake 代表。

week_list CTE 更改为这种非递归方法似乎可以解决导致 INNER JOIN 执行不正确的任何问题。我们仍然不明白为什么递归 CTE 似乎工作了几个星期然后突然开始行为不端,但如果 Snowflake 可以通过我们的支持票阐明这一点,我会加倍回到这里提供更新。感谢大家的帮助和指导!