需要将列拆分为行和列

Need to split column into rows and columns

我有一个 table 这样的:

ID  cst
1   string1;3;string2;string3;34;string4;-1;string5;string6;12;string7;5;string8,string9, 65
2   string10;-3;string11;string12;56;string13;6;string14;string15;9
etc.

现在我想将 cst 列拆分为 5 列和多行。 所以像这样:

ID  C1       C2  C3        C4        C5
1   string1   3  string2   string3   34
1   string4  -1  string5   string6   12
1   string7   5  string8   string9   65
2   string10 -3  string11  string12  56
2   string13  6  string14  string15   9
etc.

如何实现?我在 SQL-server 2017 上,所以我可以使用 string_split 功能。这个函数的问题是它只产生一个输出列...

我希望您创建一个输出 table 的 UDF。该函数将使用这些输入参数:字符串、分隔符、列数。因此该函数可以动态地用于不同数量的列。

ps。字符串当然可以是可变长度的。

老实说,这里最简单的选择可能是以下步骤:

  1. 将当前 table 写入 CSV 平面文件,使用分号作为分隔符(这也是当前 cst 列的分隔符
  2. 然后使用 SQL 服务器的批量加载工具加载 CSV,再次使用分号作为列分隔符。这将产生具有 16 列的 table,ID,然后是 C1 直到并包括 C15.
  3. 新建一个table(ID, C1, C2, C3, C4, C5)

然后填充上面的 table 使用:

INSERT INTO newTable (ID, C1, C2, C3, C4, C5)
SELECT ID, C1, C2, C3, C4, C5      FROM loadedTable UNION ALL
SELECT ID, C6, C7, C8, C9, C10     FROM loadedTable UNION ALL
SELECT ID, C11, C12, C13, C14, C15 FROM loadedTable;

虽然上述建议看起来工作量很大,但 SQL 服务器对正则表达式和复杂的字符串拆分操作的支持很差,尤其是在早期版本中。直接使用您当前的 table 可能是不可能的,或者比上面的工作更多。

试试这个:

提示:您的示例数据中有一些“正常”逗号。 我怀疑这些是错误的并使用了分号。 如果这是错误的,您可以使用一般的 REPLACE() 来使用“;”而不是“,”。

创建声明的 table 来模拟您的问题

DECLARE @tbl TABLE(ID INT, cst VARCHAR(1000));
INSERT INTO @tbl(ID,cst) 
VALUES(1,'string1;3;string2;string3;34;string4;-1;string5;string6;12;string7;5;string8;string9; 65')
     ,(2,'string10;-3;string11;string12;56;string13;6;string14;string15;9');

--查询(几乎任何版本的SQL-Server,在下面找到v2017+作为UPDATE)

WITH cte AS
(
    SELECT t.ID
          ,B.Nr
          ,A.Casted.value('(/x[sql:column("B.Nr")]/text())[1]','varchar(max)') AS ValueAtPosition
          ,(B.Nr-1) % 5 AS Position
          ,(B.Nr-1)/5 AS GroupingKey
    FROM @tbl t
    CROSS APPLY(SELECT CAST('<x>' + REPLACE(t.cst,';','</x><x>') + '</x>' AS XML)) A(Casted)
    CROSS APPLY(SELECT TOP(A.Casted.value('count(x)','int')) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) FROM master..spt_values) B(Nr)
)
SELECT ID
      ,GroupingKey
      ,MAX(CASE WHEN Position=0 THEN ValueAtPosition END) AS C1
      ,MAX(CASE WHEN Position=1 THEN ValueAtPosition END) AS C2
      ,MAX(CASE WHEN Position=2 THEN ValueAtPosition END) AS C3
      ,MAX(CASE WHEN Position=3 THEN ValueAtPosition END) AS C4
      ,MAX(CASE WHEN Position=4 THEN ValueAtPosition END) AS C5
FROM cte
GROUP BY ID,GroupingKey
ORDER BY ID,GroupingKey;

简而言之:

  • 我们使用 APPLY 将转换为 XML 的字符串添加到结果集中。这将有助于拆分字符串 ("a;b;c" => <x>a</x><x>b</x><x>c</x>)
  • 我们使用另一个 APPLY 来创建一个带有计算 TOP 子句的 动态计数 。它将 return 与 XML
  • 中的元素一样多的虚拟行
  • 我们使用 sql:column() 通过其位置获取每个元素的值,并使用一些简单的数学来创建分组键和从 0 到 4 的 运行 数字等等。
  • 我们使用 GROUP BYMAX(CASE...) 将值放在拟合列中(old-fashioned pivot条件聚合).

提示:如果你想要这个完全通用,有一些事先不知道的列。您不能使用任何类型的函数或 ad-hoc 查询。您宁愿在存储过程中需要某种动态语句创建以及 EXEC。 老实说:这可能是 XY-problem 的情况。这种方法是错误的想法 - 至少在我能想到的几乎所有情况下都是如此。

更新 SQL-Server 2017+

您使用的是 v2017,这允许 JSON,这在 安全位置 字符串拆分中更快一些。试试这个:

    SELECT t.ID
          ,A.*
    FROM @tbl t
    CROSS APPLY OPENJSON(CONCAT('["',REPLACE(t.cst,';','","'),'"]')) A

大体思路是一样的。我们将字符串转换为 JSON-array ("a,b,c" => ["a","b","c"]) 并用 APPLY OPENJSON() 读取它。 您可以在“关键”列执行相同的数学运算,然后按照上面的方法进行其余操作。

正因为这里已经准备好了,所以这是 v2017+

的完整查询
WITH cte AS
(
    SELECT t.ID
          ,A.[key]+1 AS Nr
          ,A.[value] AS ValueAtPosition
          ,A.[key] % 5 AS Position
          ,A.[key]/5 AS GroupingKey 
    FROM @tbl t
    CROSS APPLY OPENJSON(CONCAT('["',REPLACE(t.cst,';','","'),'"]')) A
)
SELECT ID
      ,GroupingKey
      ,MAX(CASE WHEN Position=0 THEN ValueAtPosition END) AS C1
      ,MAX(CASE WHEN Position=1 THEN ValueAtPosition END) AS C2
      ,MAX(CASE WHEN Position=2 THEN ValueAtPosition END) AS C3
      ,MAX(CASE WHEN Position=3 THEN ValueAtPosition END) AS C4
      ,MAX(CASE WHEN Position=4 THEN ValueAtPosition END) AS C5
FROM cte
GROUP BY ID,GroupingKey
ORDER BY ID,GroupingKey;