“逆透视”SQL 表

时间:2021-07-21 15:40:37

标签: sql sql-server tsql

我正在寻找“取消旋转”表格,但我不确定最好的方法是什么。此外,这些值由“;”分隔。我列出了我正在查看的示例:

<头>
Column_A Column_B Column_C Column_D
000 A;B;C;D 01;02;03;04 X;Y;D;E
001 A;B 05;06 S;T
002 C 07 S

从那以后,我正在寻找一种方法来取消它,但同时也要保持它当前所处的关系。例如,Column_B、C 和 D 中的第一个值被绑定在一起: |列_A|列_B|列_C|列_D| |:-|:-|:-|:-| |000|A|01|X| |000|B|02|Y| |000|C|03|D| |000|D|04|E| |001|A|05|S|

等等。

我最初的想法是使用 CTE,我已将其设置为:

WITH TEST AS(
SELECT DISTINCT Column_A, Column_B, Column_C, VALUE AS Column_D
from [TABLE]
CROSS APPLY STRING_SPLIT(Column_D, ';'))
SELECT \* FROM TEST
;

虽然这似乎不会产生正确的结果,尤其是在堆叠 CTE 和字符串拆分之后。


作为更新,下面提供了非常有用的解决方案。它们都按预期运行,但是我最后添加了一个。如果一行/列是空白的,是否可以/合理地忽略它?例如,跳过 Column_C,其中 Column_A 是“001”。 |列_A|列_B|列_C|列_D| |:-|:-|:-|:-| |000|A;B;C;D|01;02;03;04|X;Y;D;E| |001|A;B||S;T| |002|C|07|S|

3 个答案:

答案 0 :(得分:1)

您可以使用递归 CTE 来遍历字符串。假设它们的长度都相同(即分号数相同):

with cte as (
      select a, convert(varchar(max), null) as b, convert(varchar(max), null) as c, convert(varchar(max), null) as d,
             convert(varchar(max), b + ';') as rest_b, convert(varchar(max), c + ';') as rest_c, convert(varchar(max), d + ';') as rest_d,
             0 as lev
      from t
      union all
      select a,
             left(rest_b, charindex(';', rest_b) - 1),
             left(rest_c, charindex(';', rest_c) - 1),
             left(rest_d, charindex(';', rest_d) - 1),
             stuff(rest_b, 1, charindex(';', rest_b), ''),
             stuff(rest_c, 1, charindex(';', rest_c), ''),
             stuff(rest_d, 1, charindex(';', rest_d), ''),
             lev + 1
      from cte
      where rest_b <> ''
     )
select a, b, c, d
from cte
where lev > 0
order by a, lev;

Here 是一个 db<>fiddle。

编辑:

您可以使用以下方法确保过滤器使用具有相同分号数的行:

where (length(b) - length(replace(b, ';', ''))) = (length(c) - length(replace(c, ';', ''))) and
      (length(b) - length(replace(b, ';', ''))) = (length(d) - length(replace(d, ';', '')))

您还可以使用一堆分号扩展 cd,这样就不会发生错误并且结果值为空字符串。这些列中的额外分号无关紧要,因此您可以使用:

      select a, convert(varchar(max), null) as b, convert(varchar(max), null) as c, convert(varchar(max), null) as d,
             convert(varchar(max), b + ';') as rest_b, convert(varchar(max), c + replicate(';', length(b))) as rest_c, convert(varchar(max), d + replicate(';', length(b))) as rest_d,
             0 as lev

  

答案 1 :(得分:1)

这是一个基于 JSON 的方法。 SQL Server 2016 及更高版本。

SQL

-- DDL and sample data population, start
DECLARE @tbl TABLE (ColA varchar(3), ColB varchar(8000), ColC varchar(8000), ColD varchar(8000));
INSERT INTO @tbl VALUES
('000','A;B;C;D','01;02;03;04','X;Y;D;E'),
('001','A;B','05;06','S;T'),
('002','C','07','S');
-- DDL and sample data population, end

WITH rs AS 
(
     SELECT *
         , ar1 = '["' + REPLACE(ColB, ';', '","') + '"]'
         , ar2 = '["' + REPLACE(ColC, ';', '","') + '"]'
         , ar3 = '["' + REPLACE(ColD, ';', '","') + '"]'
     FROM @tbl
 )
 SELECT ColA, ColB.[value] AS [ColB], ColC.[value] AS ColC, ColD.[value] AS ColD
 FROM rs
    CROSS APPLY OPENJSON (ar1, N'$') AS ColB
    CROSS APPLY OPENJSON (ar2, N'$') AS ColC
    CROSS APPLY OPENJSON (ar3, N'$') AS ColD
 WHERE ColB.[key] = ColC.[key]
    AND ColB.[key] = ColD.[key];

输出

+------+------+------+------+
| ColA | ColB | ColC | ColD |
+------+------+------+------+
|  000 | A    |   01 | X    |
|  000 | B    |   02 | Y    |
|  000 | C    |   03 | D    |
|  000 | D    |   04 | E    |
|  001 | A    |   05 | S    |
|  001 | B    |   06 | T    |
|  002 | C    |   07 | S    |
+------+------+------+------+

答案 2 :(得分:0)

这里真正的问题是你的设计。 希望您这样做的原因是为了修复您的设计。

很遗憾,您不能为此使用 SQL Server 的内置 STRING_SPLIT,因为它不提供序数位置。因此,我使用 DelimitedSplit8K_LEAD 将值分隔成行,然后“加入”然后备份。这确实假设所有列都具有相同数量的分隔值。

CREATE TABLE dbo.YourTable (ColA varchar(3),
                            ColB varchar(8000),
                            ColC varchar(8000),
                            ColD varchar(8000));
INSERT INTO dbo.YourTable
VALUES('000','A;B;C;D','01;02;03;04','X;Y;D;E'),
      ('001','A;B','05;06','S;T'),
      ('002','C','07','S');
GO

SELECT YT.ColA,
       DSLB.Item AS ColB,
       DSLC.Item AS ColC,
       DSLD.Item AS ColD
FROM dbo.YourTable YT
     CROSS APPLY dbo.DelimitedSplit8K_LEAD(YT.ColB,';') DSLB
     CROSS APPLY dbo.DelimitedSplit8K_LEAD(YT.ColC,';') DSLC
     CROSS APPLY dbo.DelimitedSplit8K_LEAD(YT.ColD,';') DSLD
WHERE DSLB.ItemNumber = DSLC.ItemNumber
  AND DSLC.ItemNumber = DSLD.ItemNumber;
GO
DROP TABLE dbo.YourTable;