【问题标题】:Create recursive query in Snowflake with a Left Join condition?使用左连接条件在雪花中创建递归查询?
【发布时间】:2020-10-30 13:44:27
【问题描述】:

我正在尝试创建一个依赖于 LEFT JOIN 条件的递归查询,但我不确定这是否可行,尤其是在 Snowflake 中。

我有三个表:ITEMITEMHIERARCHYITEMVALUE

CREATE TABLE ITEM
(
  NAME STRING
);

INSERT INTO ITEM(NAME)
VALUES
('Item1'),('Item2'),('Item3'),('Item4'),('Item5'),('Item6');

CREATE TABLE ITEMHIERARCHY
(
 ITEM STRING,
 SUBITEM STRING 
);

INSERT INTO ITEMHIERARCHY(ITEM,SUBITEM)
VALUES
('Item2','Item3'),('Item2','Item4'),('Item4','Item5'),('Item6','Item4');

CREATE TABLE ITEMVALUE
(
  ITEM STRING,
  VALUE NUMERIC(25,10)
);

INSERT INTO ITEMVALUE(ITEM,VALUE)
VALUES
('Item1',34.2),('Item3',40.5),('Item5',20.3),('Item6',77.7);

我的目标是返回所有 ITEMs 的列表,其中包含汇总的值和子项值:

Item1, 34.2
Item2, 60.8 //roll-up of Item3 + Item4
Item3, 40.5
Item4, 20.3 //roll-up of Item5
Item5, 20.3
Item6, 77.7 //since Item6 value is given, dont roll-up from Item4

请注意,即使 Item6Item4 的汇总,因为 ITEMVALUE 表上已经存在 77.7 的给定值,但汇总会被忽略。

这是由于UNION ALL 子句中的LEFT JOIN 而导致的递归查询失败的尝试:

WITH RECURSIVE ITEMHIERARCHYFULL
  -- Column names for the "view"/CTE
  (ITEM,SUBITEM,VALUE) 
AS
  -- Common Table Expression
  (

    -- Anchor Clause
    SELECT it.NAME ITEM, ih.SUBITEM, iv.VALUE
      FROM ITEM it
      --These left-joins work
      LEFT JOIN ITEMVALUE iv ON iv.ITEM = it.NAME 
      LEFT JOIN ITEMHIERARCHY ih ON ih.ITEM = it.ITEM
                                 AND iv.VALUE IS NULL

    UNION ALL

    -- Recursive Clause
    SELECT  ihf.ITEM, ih.SUBITEM,  
      IFF(ihf.VALUE IS NOT NULL,ihf.VALUE,iv.VALUE)
      FROM ITEMHIERARCHYFULL ihf
      LEFT JOIN ITEMVALUE iv ON iv.ITEM = ihf.SUBITEM
      LEFT JOIN ITEMHIERARCHY ih ON ih.ITEM = ihf.SUBITEM
                                    AND iv.VALUE IS NULL 
  )

 -- This is the "main select".
 SELECT ITEM, SUM(VALUE) AS VALUE
 FROM ITEMHIERARCHYFULL
 GROUP BY ITEM
 ORDER BY ITEM
 ;

查询的目标是首先从ITEM表中获取所有顶级ITEMs,在ITEMVALUE表中搜索对应的值,如果没有找到,则加入ITEMHIERARCHY检索构成顶级ITEMs 的所有SUBITEMs 的表。然后我想在ITEMVALUE 表上递归搜索SUBITEM-VALUE 匹配,或者,如果没有找到匹配,则从ITEMHIERARCHY 表中检索SUBITEMs

第一组LEFT-JOINs 工作,但不是UNION ALL 下的那些给我错误:

SQL compilation error: OUTER JOINs with a self reference are not allowed in a recursive CTE.

有没有更好的方法来做我在Snowflake 中尝试做的事情,还是我没有正确考虑这个问题?

目前我手动将递归层写成 5 个级别,这意味着如果 ITEMHIERARCHY 表变得更复杂,我必须添加一个级别。

【问题讨论】:

    标签: sql left-join snowflake-cloud-data-platform recursive-query


    【解决方案1】:

    这是一个工作示例,可为您提供预期的结果。也可以在SQLFiddle查看。

    WITH CTE AS
      (
        SELECT 
            i.NAME
            , IH.SUBITEM AS descendant        
            , CASE WHEN IV.VALUE IS NULL THEN 1 ELSE 0 END AS LEVEL
        FROM ITEM AS i
        LEFT JOIN ITEMHIERARCHY AS IH
            ON i.NAME = IH.ITEM
        LEFT JOIN ITEMVALUE AS IV
            ON I.NAME = IV.ITEM
        UNION ALL
        SELECT 
            CTE.NAME
            , sIH.SUBITEM
            , 1 AS LEVEL
        FROM CTE
          INNER JOIN ITEM AS si
            ON CTE.descendant = si.NAME
          INNER JOIN ITEMHIERARCHY AS sIH
            ON si.NAME = sIH.ITEM
      ), CTE2 AS 
    (
    SELECT 
        CTE.NAME     
        , LEVEL
        , SUM(IV.VALUE) AS VALUE
        , ROW_NUMBER()OVER(PARTITION BY CTE.NAME ORDER BY CTE.LEVEL ASC) AS RNK    
    FROM CTE
    LEFT JOIN ITEMVALUE AS IV
        ON (CTE.LEVEL=0 AND CTE.NAME = IV.ITEM)
        OR (CTE.LEVEL <> 0 AND CTE.descendant = IV.ITEM)    
    GROUP BY CTE.NAME, CTE.LEVEL
    ) 
    SELECT 
        NAME
        , VALUE
    FROM CTE2
    WHERE RNK = 1
    ORDER BY 
        NAME
    ;
    

    结果:

    NAME    VALUE
    Item1   34.2000000000
    Item2   60.8000000000
    Item3   40.5000000000
    Item4   20.3000000000
    Item5   20.3000000000
    Item6   77.7000000000
    

    【讨论】:

    • 我在另一个用例中添加了Item6,以说明为什么此查询不适用于我的目的。如果在ITEMVALUE 表上给出了更高级别的项目,那么我想要该值而不是较低级别的汇总。这就是为什么LEFT-JOIN 是必要的。
    • @EliSquared 我更新了查询以满足您的新要求。我必须承认这非常复杂,如果需要,可能会进行一些代码清理。
    • 我会把你的答案标记为正确答案,因为从技术上讲,它确实做了我想做的事情,但我尝试在一个非常小的 20 行数据表上运行 Snowflake,花了超过 1 分钟执行。看起来我只需要使用手动递归层,从我的测试来看,它的执行速度要快得多。幸运的是,由于我的数据不太可能超过 5 或 6 个级别,因此它应该是可管理的。我真的希望他们在递归子句中只允许LEFT JOINs 并设置递归限制以防止∞ recursion
    【解决方案2】:

    这里有一个堆栈溢出问题,关于为什么递归查询中不允许LEFT JOINslink,基本上是为了防止∞ recursion,这是imo有点弱的原因。在第二个响应中还建议,如果您的 SQL 方言支持 OUTER APPLY,您可以使用它来代替功能等效,但 Snowflake 没有该功能。

    这是我的手动“递归”解决方案,最多可用于 3 个层次结构:

    SELECT rec.ITEM, 
      SUM(CASE
        WHEN rec.VALUE1 IS NOT NULL THEN rec.VALUE1
        WHEN rec.VALUE2 IS NOT NULL THEN rec.VALUE2
        ELSE rec.VALUE3
      END) VALUE
    
    FROM (
      SELECT it.NAME ITEM, 
      ih1.SUBITEM SUBITEM1, CASE 
                             WHEN iv1.VALUE IS NOT NULL THEN iv1.Value
                             ELSE iv1s.Value 
                            END Value1,
      ih2.SUBITEM SUBITEM2, CASE 
                             WHEN iv2.VALUE IS NOT NULL THEN iv2.Value
                             ELSE iv2s.Value 
                            END Value2,
      ih3.SUBITEM SUBITEM3, CASE 
                             WHEN iv3.VALUE IS NOT NULL THEN iv3.Value
                             ELSE iv3s.Value 
                            END Value3
      
      FROM ITEM it
    
      LEFT JOIN ITEMVALUE iv1 ON iv1.ITEM = it.NAME 
      LEFT JOIN ITEMHIERARCHY ih1 ON ih1.ITEM = it.NAME
                                 AND iv1.VALUE IS NULL
      LEFT JOIN ITEMVALUE iv1s ON iv1s.ITEM = ih1.SUBITEM
    
      LEFT JOIN ITEMVALUE iv2 ON iv2.ITEM = ih1.SUBITEM 
      LEFT JOIN ITEMHIERARCHY ih2 ON ih2.ITEM = ih1.SUBITEM
                                 AND iv1.VALUE IS NULL
                                 AND iv1s.VALUE IS NULL
                                 AND iv2.VALUE IS NULL
      LEFT JOIN ITEMVALUE iv2s ON iv2s.ITEM = ih2.SUBITEM
                                 
      LEFT JOIN ITEMVALUE iv3 ON iv3.ITEM = ih2.SUBITEM 
      LEFT JOIN ITEMHIERARCHY ih3 ON ih3.ITEM = ih2.SUBITEM
                                 AND iv1.VALUE IS NULL
                                 AND iv1s.VALUE IS NULL
                                 AND iv2.VALUE IS NULL
                                 AND iv2s.VALUE IS NULL
                                 AND iv3.VALUE IS NULL
      LEFT JOIN ITEMVALUE iv3s ON iv3s.ITEM = ih3.SUBITEM
    ) rec
    
    WHERE CASE
        WHEN VALUE1 IS NOT NULL THEN VALUE1
        WHEN VALUE2 IS NOT NULL THEN VALUE2
        ELSE VALUE3
      END IS NOT NULL
    
    GROUP BY ITEM
    

    这显然是一种语法上非常低效的方法,在每个步骤中,您必须同时检查 ITEMSUBITEM 值,然后对之前的每个 ITEMVALUESUBITEMVALUE 表重复 NULL 检查。我为每个级别输入了SUBITEMs,因此如果您只运行查询的内部部分,您可以看到扩展是如何工作的。我还必须使用CASE 语句来让事情在 SQLFIDDLE 上运行,但我更愿意使用IFFIFNULL(Value1,IFNULL(Value2,Value3)).

    这是 SQL Fiddle 上的工作代码:link 和输出:

    Item1, 34.2
    Item2, 60.8
    Item3, 40.5
    Item4, 20.3
    Item5, 20.3
    Item6, 77.7
    

    【讨论】:

      猜你喜欢
      • 2020-11-05
      • 1970-01-01
      • 2015-11-25
      • 1970-01-01
      • 2021-08-09
      • 2021-07-22
      • 1970-01-01
      • 1970-01-01
      • 2020-12-03
      相关资源
      最近更新 更多