基于层次结构的数据透视数据

时间:2018-05-11 16:02:16

标签: sql sql-server pivot-table sql-server-2014 hierarchy

我有一个分层数据,其结构可能会发生变化。关系在单个表中维护,通过两列上的自引用标识,节点ID和父ID。我希望能够运行查询来转动数据,以便每行代表节点的最低单位。

例如:

如果我有一张看起来像这样的桌子......

enter image description here

我希望能够达到这个目标......

enter image description here

为了让所有事情都在同一条线上,我已经做了几次连接......

SELECT L1.NAME AS CITY, L2.NAME AS COUNTY, L3.NAME AS STATE, L4.NAME AS 
COUNTRY
FROM TABLENAME L1
LEFT JOIN TABLENAME AS L2 ON L1.PARENT_NODE_ID = L2.NODE_ID
LEFT JOIN TABLENAME AS L3 ON L2.PARENT_NODE_ID = L3.NODE_ID
LEFT JOIN TABLENAME AS L4 ON L3.PARENT_NODE_ID = L4.NODE_ID
WHERE L1.Type = City

这是问题的核心:我可能并不总是知道层次结构。因此,我需要一个可以处理变化的解决方案。假设业务逻辑的守护者决定我们需要在国家之上添加Hemisphere。或州内的一个地区(西海岸,中部,东海岸)。然而,城市将始终是最低节点。我需要能够独立于层次结构而存在的东西。

更新 我原来的问题我用了一个简单的例子。在我的实际解决方案中,我必须利用多个连接来获得我需要的层次结构。我正在研究波纹管查询,但截至目前,它为我希望填充的每一列返回null。最有可能是案例陈述的问题?

;WITH ALLORGS AS( --All Orgs
    SELECT ORGS.ID, ORGS.ORG_NAME
        , HIER.ID_PARENTORG, TYP.ORG_TYPE_DESCR
        FROM ORGANIATIONS AS ORGS
        FULL OUTER JOIN HIERARCHYTABLE AS HIER ON ORGS.ID = HIER.ID_ORG
        FULL OUTER JOIN ORGANIZATION_TYPES AS TYP ON ORGS.ID_ORG_TYPE = TYP.ID

), CTE AS ( 

    SELECT ID
    , ID_PARENTORG
    , L1.ORG_NAME 
    --, ORG_TYPE_DESCR
    , CAST('' as varchar(100)) AS UNIT
    , CAST('' as varchar(100)) AS REGION
    , CAST('' as varchar(100)) AS DDA_POOL
    , CAST('' as varchar(100)) AS COUNTY
    , CAST('' as varchar(100)) AS STATE
    , CAST('' as varchar(100)) AS BUSINESS_UNIT
    , CAST('' as varchar(100)) AS PROEPRTY
    , CAST('' as varchar(100)) AS DISTRICT
    , 1 AS FLAG

    FROM ALLORGS L1
    WHERE L1.ORG_TYPE_DESCR = 'COST CENTER'

    UNION ALL

    SELECT T1.ID
    ,L2.ID_PARENTORG
    ,T1.ORG_NAME AS COSTCNTR
    --, T.ORG_TYPE_DESCR
    ,CASE WHEN L2.ORG_TYPE_DESCR = 'UNIT' THEN L2.ORG_NAME ELSE NULL END AS UNIT
    ,CASE WHEN L2.ORG_TYPE_DESCR = 'REGION' THEN L2.ORG_NAME ELSE NULL END AS REGION
    ,CASE WHEN L2.ORG_TYPE_DESCR = 'DDA_POOL' THEN L2.ORG_NAME ELSE NULL END AS DDA_POOL
    ,CASE WHEN L2.ORG_TYPE_DESCR = 'COUNTRY' THEN L2.ORG_NAME ELSE NULL END AS COUNTRY
    ,CASE WHEN L2.ORG_TYPE_DESCR = 'STATE' THEN L2.ORG_NAME ELSE NULL END AS STATE
    ,CASE WHEN L2.ORG_TYPE_DESCR = 'BUSINESS_UNIT' THEN L2.ORG_NAME ELSE NULL END AS BUSINESS_UNIT
    ,CASE WHEN L2.ORG_TYPE_DESCR = 'PROPERTY' THEN L2.ORG_NAME ELSE NULL END AS PROPERTY
    ,CASE WHEN L2.ORG_TYPE_DESCR = 'DISTRICT' THEN L2.ORG_NAME ELSE NULL END AS DISTRICT
    ,T1.FLAG + 1 AS FLAG

    FROM CTE AS T1 
    INNER JOIN ALLORGS AS L2 ON T1.ID_PARENTORG = L2.ID 
)
SELECT a.ID
,a.ORG_NAME AS COSTCNTR
,UNIT
,REGION
,DDA_POOL
,COUNTY
,STATE
,BUSINESS_UNIT
,PROEPRTY
,DISTRICT
FROM CTE AS a
INNER JOIN (SELECT ID, MAX(FLAG) FLAG FROM CTE GROUP BY ID) b ON a.ID = b.ID AND a.FLAG = b.FLAG  

2 个答案:

答案 0 :(得分:4)

试试这个...... 在使用之前,请使用更多样本数据对其进行测试。

表格脚本和示例数据

CREATE TABLE [TableName](
    [ParentNodeID] [int] NULL,
    [NodeID] [int] NULL,
    [Type] [nvarchar](50) NULL,
    [Name] [nvarchar](50) NULL
) 

INSERT [TableName] ([ParentNodeID], [NodeID], [Type], [Name]) VALUES (NULL, 1, N'Country', N'US')
INSERT [TableName] ([ParentNodeID], [NodeID], [Type], [Name]) VALUES (1, 2, N'State', N'Texas')
INSERT [TableName] ([ParentNodeID], [NodeID], [Type], [Name]) VALUES (2, 3, N'County', N'Dallas')
INSERT [TableName] ([ParentNodeID], [NodeID], [Type], [Name]) VALUES (3, 4, N'City', N'Dallas')
INSERT [TableName] ([ParentNodeID], [NodeID], [Type], [Name]) VALUES (NULL, 1, N'Country', N'US')
INSERT [TableName] ([ParentNodeID], [NodeID], [Type], [Name]) VALUES (5, 6, N'State', N'Massachusetts')
INSERT [TableName] ([ParentNodeID], [NodeID], [Type], [Name]) VALUES (7, 8, N'County', N'Suffolk')
INSERT [TableName] ([ParentNodeID], [NodeID], [Type], [Name]) VALUES (9, 10, N'City', N'Boston')

<强>查询

DECLARE @cols AS NVARCHAR(max) = Stuff((SELECT DISTINCT ',' + Quotename([Type])
         FROM   TableName       
         FOR xml path(''), type).value('.', 'NVARCHAR(MAX)'), 1, 1, ''); 

DECLARE @query AS NVARCHAR(max) =  'SELECT max(NodeID)    AS NodeID
                                          ,max([Country]) AS Country
                                          ,max([State])   AS STATE
                                          ,max([County])  AS County
                                          ,max([City])    AS City
                                    FROM (
                                        SELECT *, Row_Number() OVER (PARTITION BY Type ORDER BY NodeID) rn
                                        FROM TableName
                                        ) sq
                                    pivot(max([Name]) FOR [Type] IN ('+ @cols +') ) pvt
                                    GROUP BY rn';

EXECUTE(@query) 

<强>输出

+--------+---------+---------------+---------+--------+
| NodeID | Country |     STATE     | County  |  City  |
+--------+---------+---------------+---------+--------+
|      4 | US      | Texas         | Dallas  | Dallas |
|     10 | US      | Massachusetts | Suffolk | Boston |
+--------+---------+---------------+---------+--------+

在线演示:http://www.sqlfiddle.com/#!18/7470b/3/0

答案 1 :(得分:2)

我也已经实现了相同的情况,我没有测试这个,因为我现在只在这里使用我的手机,但几乎我使用的逻辑是这样的,使用CTE做递归,希望这也有效

WITH CTE AS (
--Put Initial Value '' to be filled later
SELECT NODE_ID, PARENT_ID, L1.NAME AS CITY, '' AS COUNTY, '' AS STATE, '' AS COUNTRY,
--add hemisphere
'' AS HEMISPHERE,
1 AS FLAG --Only for indication of looping
FROM TABLENAME L1
WHERE L1.TYPE = 'CITY'

UNION ALL

SELECT T1.NODE_ID, L2.PARENT_ID, 
T1.NAME AS CITY, 
(CASE WHEN L2.TYPE = 'COUNTY' THEN L2.NAME ELSE T1.NAME) AS COUNTY, 
(CASE WHEN L2.TYPE = 'STATE' THEN L2.NAME ELSE T1.NAME) AS STATE, 
(CASE WHEN L2.TYPE = 'COUNTRY' THEN L2.NAME ELSE T1.NAME) AS COUNTRY
--and can add some more columns here,  in case if there is additional column for Hemisphere
 (CASE WHEN L2.TYPE = 'Hemisphere' THEN L2.NAME ELSE T1.NAME) AS Hemisphere
T1.FLAG + 1 AS FLAG -- add +1 for n reccuring, only for indication of looping
FROM CTE T1
INNER JOIN TABLENAME L2 ON T1.PARENT_ID = 
L2.NODE_ID
)

SELECT a.NODE_ID, CITY, COUNTY, STATE, COUNTRY
FROM CTE a
--to get the last loop which has completely filled data
INNER JOIN (SELECT NODE_ID, MAX(FLAG) FLAG FROM CTE GROUP BY NODE_ID ) b ON a.NODE_ID = b.NODE_ID AND a.FLAG = b.FLAG