【问题标题】:Two queries returning different results when they should be equivalent?两个查询在它们应该相等时返回不同的结果?
【发布时间】:2020-01-27 23:20:40
【问题描述】:

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

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

原始查询:

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 中指定的周数)。

“固定”查询:

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

结果:返回四行,日期为 2019 年 12 月 30 日至 2020 年 1 月 20 日的周数,包含适当的节数。

【问题讨论】:

  • 这可能是一个愚蠢的问题,但是您是否有一个名为 week_list 的表以及一个同名的 CTE?
  • 仅用于检查视图合并是否正在发生如果您在 active_sections_per_week 中添加“WHERE 1=1”会在原始查询中发生什么
  • 好问题@MikeWalton!我没有单独的同名表,week_list 这里总是指 CTE。
  • 感谢@GeorgeJoseph 的建议!我尝试在active_sections_per_week CTE 中添加WHERE 1=1,但结果相同(仅返回一周)。
  • 如果你尝试 WHERE CURRENT_DATE() = CURRENT_DATE() 祝你好运

标签: sql inner-join snowflake-cloud-data-platform


【解决方案1】:

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

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

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

至于为什么它之前起作用,很可能是表状态和递归 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

【讨论】:

  • 感谢您的留言!抱歉,我在原来的帖子中不太清楚——尽管这个查询在 Redshift 中“有效”,但它需要大量小的语法更改以及一个重大更改:必须修改 CTE,以便创建日期列表(正如您所说,这是不同的,因为 Redshift 不支持这种递归)。我将查询的那部分更改为:SELECT DATEADD('week', -n, DATE_TRUNC('week', CURRENT_DATE)) AS week_value FROM (SELECT ROW_NUMBER() OVER () AS n FROM stl_scan LIMIT 4) n ORDER BY week_value DESC
  • 我会查看您提供的链接(谢谢!)但是您是否有理由认为是递归 CTE 专门导致了问题?我真的很想理解这个问题,因为这个查询中涉及到其他内部连接(它比提供的 sn-p 更长、更复杂,sn-p 只是触及了问题的核心)而且我担心其他联接可能会突然“中断”,因为我一开始并不了解问题所在。
  • 嗨,维克。如果没有基础数据来测试它,CTE 上的递归是一个有根据的猜测,为什么它在内部联接与带有 where 子句的左联接的行为不同。阅读一些 cmets,您似乎有一个需要支持的案例。我认为让他们看一看并了解整个情况可以做出决定是个好主意。
【解决方案2】:

递归 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 的建议。它似乎已经成功了!再次感谢!
【解决方案3】:

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

以下是我的发现:虽然递归适用于我应用它的用例(根据CURRENT_DATE 生成日期列表),但这并不是绝对必要的。由于我们想要一个日期列表,我可以轻松地生成一个表格并使用行号来执行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)的限制。由于我使用这些数据来创建一段时间内的活动图表,因此拥有 200 个值可以让我拥有超过 3 年的历史,而不仅仅是 2 年的历史。如果我想扩展它,我也不必联系我的 Snowflake 代表。

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

【讨论】:

  • 啊,很有趣你使用了一个生成器,因为这是我们使用的,但我简化了它,因为你的示例有 4,并且常量值在那里也能正常工作..,但很高兴看到您找到了一个对您更有用的解决方案。
猜你喜欢
  • 2012-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-04
  • 1970-01-01
相关资源
最近更新 更多