按定界符列名和列标题将行转置为列

时间:2019-06-22 09:54:12

标签: sql sql-server tsql

我在这个问题上有点卡住,请需要一些SQL帮助。 iv从需要帮助的人那里得到了一个层次结构表,并将其放入另一个表的实际列中。这是查询表的示例行。除了从此表结果中进行工作之外,我无权进行任何调整:

SELECT ID, Path, Path_Values 
FROM TABLE1

输出:

ID  | Path                      | Path_Data_Values
----+---------------------------+----------------------------------
1   | Root                      | Org
2   | Root / Hemisphere         | Org / North Hemisphere
3   | Root / Hemisphere / State | Org / North Hemisphere / Texas
4   | Root / State              | Org / Texas

上表可以深入到大约10个级别。无论如何,值得庆幸的是,我知道决赛桌的最大深度,而且我可以访问将结果存储在上面的位置,将其转​​换为上面的结果以读取路径并为其确定合适的列,然后将值插入到最终表中。

此床头柜的可视示例(所需结果):

ID | Root | Hemisphere           | State | Other_1 | Other_2 | Other_3
---+------+----------------------+-------+---------+---------+----------
 1 | Org  | NULL                 | NULL  | NULL    | NULL    | NULL
 2 | Org  | Northern Hemisphere  | NULL  | NULL    | NULL    | NULL
 3 | Org  | Northern Hemisphere  | Texas | NULL    | NULL    | NULL
 4 | Org  | NULL                 | Texas | NULL    | NULL    | NULL

3 个答案:

答案 0 :(得分:4)

这可以帮助您实现大部分目标。但是,由于数据的定义在行与行之间会发生变化,因此,如果要动态执行操作,则无法按所需顺序获取数据。 (例如,示例应处于2或3的位置,因为这两个位置都显示?没有数据或查询表的知识,那是不可能的。这是一个动态解决方案,但是,它向您显示了生成的代码如果您想手动编码所有职位,则可以获得您想要的结果。

这也使用了DelimitedSplit8k_LEAD,因为STRING_SPLIT没有提供分隔列表中某项的顺序位置;使其在这里无用。

CREATE TABLE dbo.YourTable (ID int,
                            [Path] varchar(8000),
                            Path_Data_Values varchar(8000));
INSERT INTO dbo.YourTable(ID, [Path], Path_Data_Values)
VALUES (1,'Root','Org'),
       (2,'Root / Hemisphere','Org / North Hemisphere'),
       (3,'Root / Hemisphere / State','Org / North Hemisphere / Texas'),
       (4,'Root / State','Org / Texas');

GO

DECLARE @SQL nvarchar(MAX);

SET @SQL = N'SELECT YT.ID,' + NCHAR(13) + NCHAR(10) +
           STUFF((SELECT N',' + NCHAR(13) + NCHAR(10) + 
                         N'       MAX(CASE P.Item WHEN ' + QUOTENAME(P.Item,'''') + N' THEN PDV.Item END) AS ' + QUOTENAME(P.Item)
                  FROM dbo.YourTable YT
                       CROSS APPLY (VALUES(REPLACE(YT.[Path],' / ','|')))V([Path])
                       CROSS APPLY dbo.DelimitedSplit8K_LEAD(V.[Path],'|') P
                  GROUP BY P.Item
                  FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,3,N'') + NCHAR(13) + NCHAR(10) +
           N'FROM dbo.YourTable YT' + NCHAR(13) + NCHAR(10) +
           N'     CROSS APPLY (VALUES(REPLACE(YT.[Path],'' / '',''|''),REPLACE(YT.Path_Data_Values,'' / '',''|'')))V([Path],Path_Data_Values)' + NCHAR(13) + NCHAR(10) +
           N'     CROSS APPLY dbo.DelimitedSplit8K_LEAD(V.[Path],''|'') P' + NCHAR(13) + NCHAR(10) +
           N'     CROSS APPLY(SELECT DS.Item FROM dbo.DelimitedSplit8K_LEAD(V.Path_Data_Values,''|'') DS WHERE P.ItemNumber = DS.ItemNumber) PDV' + NCHAR(13) + NCHAR(10) +
           N'GROUP BY YT.ID;';

PRINT @SQL;

EXEC sp_executesql @SQL;

答案 1 :(得分:2)

由于Larnu的示例数据,我得以发布另一种方式来实现此目的。
这种方法是构造一个gulp-string-replace子句以使用动态SQL进行选择。

values

给定样本数据的-- Get the maximum number of columns DECLARE @ColumnsCount int = (SELECT MAX(LEN(Path_Data_Values) - LEN(REPLACE(Path_Data_Values, ' / ', ' '))) FROM YourTAble ) DECLARE @Sql nvarchar(max) = 'SELECT * FROM (VALUES' + STUFF( ( -- Construct the values clause SELECT ',(''' + REPLACE(data, ' / ', ''',''') + ''')' FROM ( -- use Replicate to add the "missing" values to each line SELECT Path_Data_Values + REPLICATE(' / ', @ColumnsCount - (LEN(Path_Data_Values) - LEN(REPLACE(Path_Data_Values, ' / ', ' ')))) As data FROM YourTAble ) x FOR XML PATH('') ), 1, 1, '') + ') V (' + STUFF( ( -- Construct the values names SELECT ',' + REPLACE(Path, ' / ', ',') FROM YourTAble WHERE LEN(Path) - LEN(REPLACE(Path, ' / ', ' ')) = @ColumnsCount FOR XML PATH('') ), 1, 1, '') + ')'; -- Whenever using dynamic SQL, Print is your best friend PRINT @Sql -- this will print, with the sample data provided, the following SQL statement: -- SELECT * FROM (VALUES('Org','',''),('Org','North Hemisphere',''),('Org','North Hemisphere','Texas'),('Org','Texas','')) V (Root,Hemisphere,State) -- unremark once print gets you the desired sql --EXEC(@Sql) 的结果将是这样:

exec(sql)

如果要使用Root Hemisphere State Org Org North Hemisphere Org North Hemisphere Texas Org Texas 而不是空字符串,就像在构造的动态SQL中将null替换为''一样简单。

请注意,对于2017版或更高版本,您可以使用string_agg代替我以前使用的nullstuff组合来简化此操作。

You can see a live demo on rextester.

答案 2 :(得分:0)

是否可以创建一个行号表然后进行数据透视?

例如

with cte_t
as
(
    Select * From
    (
    values(1,'Root','Org'),(2,'Root / Hemisphere','Org / North Hemisphere'),(3,'Root / Hemisphere / State','Org / North Hemisphere / Texas'),(4,'Root / State','Org / Texas')
    ) as t (ID,ThePath,Path_data_Values)
)

SELECT t1.[Id], [x].[value] as ThePath, x.[rn1], [x2].[value] as Path_data_Values, x2.[rn2] -- t1.ThePath,t1.Path_data_Values
FROM cte_t AS t1
CROSS APPLY (SELECT v1.[value], ROW_NUMBER() OVER (PARTITION BY t1.[Id] ORDER BY t1.[Id]) AS RN1 FROM STRING_SPLIT(t1.ThePath, '/') AS [v1]) AS x
CROSS APPLY (SELECT v2.[value], ROW_NUMBER() OVER (PARTITION BY t1.[Id] ORDER BY t1.[Id]) AS RN2 FROM STRING_SPLIT(t1.Path_data_Values, '/') AS [v2]) AS x2