使用T-SQL高效地从父子表构建DWH

时间:2017-10-11 16:19:41

标签: sql sql-server

过去两天,我一直试图找到一个尽可能最有活力的有效解决方案,但我无法做到。您是否有以下示例的提示?我尝试了一个递归调用来获得"等级"或者到目前为止的本机连接以及下面的结果。

我有" initTable":

parent | child
-------+------
   A   |  H
   A   |  B
   A   |  C
   B   |  D
   B   |  G
   C   |  F
   D   |  E

我想要" finalTable" :(在bescase中动态创建列" levelX"

level1 | level2 | level 3 | level 4
-------+--------+---------+---------
   A   |   A    |   A     |   H
   A   |   A    |   C     |   F
   A   |   A    |   B     |   G
   A   |   B    |   D     |   E

尝试#1 :获取级别的递归方式

问题:是否有可能在递归期间为每个级别创建一列?

WITH recTable (father, child, lev) AS 
(
    SELECT
        p1.father,
        p1.child,
        0 as lev
    FROM 
        initTable AS p1 
    WHERE 
        p1.father = 'A' 

    UNION ALL

    SELECT
        p1.father,
        p1.child,
        lev+1
    FROM 
        initTable AS p1 
    INNER JOIN 
        recTable as p2 ON p1.father = p2.child
) 
SELECT * FROM ASD

尝试#2 :但是"错误"为了

在这里,我需要填写列#34;向后"不知何故...

SELECT
    p1.child AS Level 1,
    p2.child AS Level 2,
    p3.child AS Level 3,
    p4.child AS Level 4
FROM 
    initTable p1 
LEFT JOIN 
    initTable p2 ON p1.child = p2.father
LEFT JOIN 
    initTable p3 ON p2.child = p3.father
LEFT JOIN 
    initTable p4 ON p3.child = p4.father
WHERE
     p1.father= 'A'

有人知道解决此问题的有效方法吗?我觉得我非常接近,但到目前为止我无法解决它。

1 个答案:

答案 0 :(得分:0)

以下是动态的,适用于您的示例,以及任何具有更多/更少父/子关系的示例:

-- Get the number of required fields

SELECT
    parent
    , child
    , 1 [level]
INTO #checking
FROM initTable
WHERE parent NOT IN (SELECT child FROM initTable)
;

WHILE EXISTS (SELECT * FROM #checking c JOIN initTable t ON c.child = t.parent)
BEGIN

    INSERT INTO #checking

    SELECT
        t.parent
        , t.child
        , (SELECT MAX([level]) + 1 FROM #checking)
    FROM
        #checking c
        JOIN initTable t ON c.child = t.parent
    ;

    DELETE
    FROM #checking
    WHERE [level] <> (SELECT MAX([level]) FROM #checking)
    ;

END
;

DECLARE @requiredLevels int = (SELECT TOP 1 [level] + 1 FROM #checking)
;

DROP TABLE #checking
;

-- Build a dynamic statement for the SELECT fields

DECLARE
    @fieldSQL varchar(1000) = ''
    , @fieldsN int = @requiredLevels
;

WHILE @fieldsN > 0
BEGIN

    IF @fieldsN > 2
        BEGIN

            DECLARE
                @coalesceFields varchar(1000) = ''
                , @coalesceN int = @fieldsN

            WHILE @coalesceN > 1
            BEGIN

                IF @coalesceFields = ''
                    SET @coalesceFields = 'L' + CAST(@coalesceN AS varchar) + '.parent'
                ELSE
                    SET @coalesceFields = @coalesceFields + ', L' + CAST(@coalesceN AS varchar) + '.parent'
                ;

                SET @coalesceN = @coalesceN - 1

            END

            SET @fieldSQL = @fieldSQL + ', COALESCE(' + @coalesceFields + ') [level ' + CAST(@requiredLevels - @fieldsN + 1 AS varchar) + ']'

        END
    ELSE
        SET @fieldSQL = @fieldSQL + ', L' + CAST(@fieldsN AS varchar) + '.' + CASE WHEN @fieldsN = 1 THEN 'child' ELSE 'parent' END + ' [level ' + CAST(@requiredLevels - @fieldsN + 1 AS varchar) + ']'
    ;

    SET @fieldsN = @fieldsN - 1

END

SET @fieldSQL = SUBSTRING(@fieldSQL, 3, LEN(@fieldSQL) - 2)

-- Build a dynamic statement for the LEFT JOINs

DECLARE
    @joinSQL varchar(1000) = ''
    , @joinsN int = 2

WHILE @joinsN <= @requiredLevels
BEGIN

    SET @joinSQL = @joinSQL + ' LEFT JOIN initTable L' + CAST(@joinsN AS varchar) + ' ON L' + CAST(@joinsN - 1 AS varchar) + '.' + CASE WHEN @joinsN = 2 THEN 'child' ELSE 'parent' END + ' = L' + CAST(@joinsN AS varchar) + '.child'

    SET @joinsN = @joinsN + 1

END

-- Build the final SQL statement and execute

DECLARE @SQL varchar(8000) =
    '
        SELECT ' + @fieldSQL + '
        FROM
            (
                SELECT child
                FROM initTable
                WHERE child NOT IN (SELECT parent FROM initTable)
            ) L1' + @joinSQL

EXEC (@SQL)