需要将列分为行和列

问题描述

我有一张这样的桌子:

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函数。此函数的问题在于它仅产生一个输出列...

最好我想创建一个输出表的UDF。该函数将使用以下输入参数:字符串,分隔符,列数。因此,该函数可以动态地用于不同数量的列。

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

解决方法

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

  1. 使用分号作为分隔符(也是当前cst列的分隔符,将当前表写到CSV平面文件中
  2. 然后使用SQL Server的批量加载工具加载CSV,再次使用分号作为列分隔符。这将产生一个包含16列ID,然后C1C15并包括(ID,C1,C2,C3,C4,C5)的表。
  3. 创建一个新表INSERT INTO newTable (ID,C5) SELECT ID,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;

然后使用以下命令填充上表:

bazel info

尽管上述建议似乎需要大量工作,但SQL Server对正则表达式和复杂的字符串拆分操作的支持不佳,尤其是在早期版本上。可能无法直接使用当前表进行操作,或者可能无法完成以上工作。

,

尝试以下方法:

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

创建一个声明的表来模拟您的问题

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,请在下面的UPDATE中找到v2017 +)

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子句的 tally 。它将返回与XML中的元素一样多的虚拟行。
  • 我们使用sql:column()来获取每个元素的位置值,并使用一些简单的数学方法来创建分组键和0到4之间的一个连续数字,依此类推。
  • 我们将GROUP BYMAX(CASE...)一起使用,将值放在拟合列中(老式枢轴条件聚合)。 / li>

提示:如果要完全通用地使用,则不事先知道一些列。您不能使用任何类型的功能或即席查询。您宁愿在存储过程中与EXEC一起进行某种动态语句创建。 老实说:这可能是XY问题的情况。这种方法是错误的想法-至少在我能想到的几乎所有情况下。

SQL-Server 2017+的更新

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

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

总体思路是相同的。我们将字符串转换为JSON数组(“ 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('["','"]')) A
)
SELECT ID,GroupingKey;