Создать рекурсивный запрос в Snowflake с условием левого соединения? - PullRequest
2 голосов
/ 10 июля 2020

Я пытаюсь создать рекурсивный запрос, основанный на условии LEFT JOIN, но не уверен, возможно ли это, особенно в Snowflake.

У меня есть три таблицы: ITEM, ITEMHIERARCHY и ITEMVALUE

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

Обратите внимание, что несмотря на то, что Item6 - это свертка из Item4, потому что в таблице ITEMVALUE уже есть заданное значение 77.7, свертка игнорируется.

Вот моя попытка неудачный рекурсивный запрос из-за LEFT JOIN в предложении UNION ALL:

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
 ;

Цель запроса - сначала получить весь верхний уровень ITEMs из таблицы ITEM, поиск соответствующее значение в таблице ITEMVALUE, и, если ничего не найдено, присоединитесь к таблице ITEMHIERARCHY, чтобы получить все SUBITEMs, составляющие верхний уровень ITEMs. Затем я хотел бы рекурсивно искать в таблице ITEMVALUE совпадение SUBITEM-VALUE или, если ничего не найдено, извлечь SUBITEMs из таблицы ITEMHIERARCHY.

Первый набор LEFT-JOINs работают, но не те, что под UNION ALL, что дает мне ошибку:

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

Есть ли лучший способ сделать то, что я пытаюсь сделать в Snowflake, или я не думаю о это правильно?

В настоящее время я вручную выписал рекурсивные слои на 5 уровней, что означает, что мне нужно добавить уровень, если таблица ITEMHIERARCHY станет более сложной.

Ответы [ 2 ]

0 голосов
/ 14 июля 2020

Вот вопрос о переполнении стека о том, почему LEFT JOINs не разрешены в рекурсивных запросах: link , в основном это для предотвращения ∞ recursion, что является немного слабой причиной imo. Во втором ответе также предлагается, что если ваш SQL диалект поддерживает OUTER APPLY, вы можете использовать его вместо этого для функциональной эквивалентности, но Snowflake не имеет этой функции.

Вот мое ручное «рекурсивное» решение для трех уровней иерархии:

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

Это, очевидно, синтаксически очень неэффективный подход, когда на каждом шаге вы должны проверять оба значения ITEM И SUBITEM, а затем повторять NULL проверяет каждую предыдущую ITEMVALUE или SUBITEMVALUE таблицу. Я добавляю SUBITEMs для каждого уровня, поэтому, если вы выполняете только внутреннюю часть запроса, вы можете увидеть, как работает расширение. Мне также пришлось использовать оператор CASE, чтобы все работало с SQLFIDDLE, но я бы предпочел использовать IFF и IFNULL(Value1,IFNULL(Value2,Value3)).

Вот рабочий код SQL Fiddle: ссылка и вывод:

Item1, 34.2
Item2, 60.8
Item3, 40.5
Item4, 20.3
Item5, 20.3
Item6, 77.7
0 голосов
/ 10 июля 2020

Вот рабочий пример, который дает вам ожидаемые результаты. Вы также можете просмотреть его на 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...