T-SQL 将逗号分隔列转换为多列 - 动态

T-SQL Convert Comma Delimited column into multiple columns - Dynamic

SQL Server 2008 R2,我有一个 table 和一个 varchar 列,其中包含一个逗号分隔的字符串。逗号分隔字段的数量可能会有所不同,从几个到几十个 (50+)。我需要将 CSV 字符串转换为列。 table 数据示例:

DECLARE @tv_Table1 TABLE 
                   (
                       Name VARCHAR(MAX),
                       [Values] VARCHAR(MAX)
                   );

INSERT INTO @tv_Table1 
VALUES ('Number1', '00,1110801100543 20190801103956,123456798708/01/201910:07:13,123456787608/01/201910:08:26,123456776508/01/201910:08:56,123456755508/01/201910:09:26,SOME TEXT1,123456744408/01/201910:29:08,'),
       ('Number2', 'val,val2,val3,'),
       ('Number3', 'val5,val7,val9,val14,'),
       ('Number4', '00,8980801100904 20190801128781,777333489808/01/201910:07:13,777333489808/01/201910:08:26,777333489808/01/201910:08:56,777333489808/01/201910:09:26,777333489808/01/201910:10:14,SOME TEXT2,777333489808/01/201910:05:56,777333489808/01/201910:12:05,777333489808/01/201910:12:44,777333489808/01/201910:13:02,777333489808/01/201910:13:02,777333489808/01/201910:13:32,777333489808/01/201910:14:03,777333489808/01/201910:15:15,777333489808/01/201910:15:46,SOME TEXT2,777333489808/01/201910:19:08,777333489808/01/201910:19:38,SOME TEXT2,777333489808/01/201910:28:31,777333489808/01/201910:29:02,777333489808/01/201910:29:32,777333489808/01/201910:30:02,777333489808/01/201910:30:32,777333489808/01/201910:31:02,777333489808/01/201910:31:32,777333489808/01/201910:32:02,777333489808/01/201910:32:33,777333489808/01/201910:33:02,777333489808/01/201910:33:33,777333489808/01/201910:34:11,SOME TEXT2,777333489808/01/201910:36:47,777333489808/01/201910:37:17,777333489808/01/201910:37:35,')

SELECT 
    Name,
    Commas = LEN([Values]) - LEN(REPLACE([Values], ',', '')),
    [Values]
FROM 
    @tv_Table1

我需要分成多列的字符串中的最后一个字符始终是逗号 (,)。

我需要这样的输出:

我开始使用 CTE:

;WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A, L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A, L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),
Numbers AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS n FROM L3)
SELECT 
Name, 
[1] AS Column1, [2] AS Column2, [3] AS Column3, [4] AS Column4, [5] AS Column5, 
[6] AS Column6, [7] AS Column7,[8] AS Column8, [9] AS Column9, [10] AS Column10, 
[11] AS Column11, [12] AS Column12, [13] AS Column13, [14] AS Column14,[15] AS Column15
FROM
(SELECT Name,
        ROW_NUMBER() OVER (PARTITION BY Name ORDER BY nums.n) AS PositionInList,
        LTRIM(RTRIM(SUBSTRING(valueTable.[Values], nums.n, charindex(N',', valueTable.[Values] + N',', nums.n) - nums.n))) AS [Value]
 FROM   Numbers AS nums INNER JOIN @tv_Table1 AS valueTable ON nums.n <= CONVERT(int, LEN(valueTable.[Values])) AND SUBSTRING(N',' + valueTable.[Values], n, 1) = N',') AS SourceTable
PIVOT
(
MAX([VALUE]) FOR PositionInList IN (
[1], [2], [3], [4], [5], 
[6], [7], [8], [9], [10],
[11], [12], [13], [14], [15]

)
) AS Table2

但是,这有几个问题。第一,我突然意识到它不是动态的(数据可以是);第二,到目前为止我停在了 11(Number4 应该有 37 列)。

有没有办法让它变得动态?有没有更好的方法来解决这个问题?有没有办法让我到目前为止的工作?

我有一些限制 - 我无法更改 table 结构。我无法添加功能。如果需要,我应该能够添加存储过程,但我不想这样做。

感谢任何想法、帮助、建议等。

查询将 运行 按需查询,通常是一天,大约 500 行,其中可能有十几行需要 CSV 字符串到列。

使用临时table,可以构建动态查询。 temp table 保存解析原始数据。然后获取列表中的最大位置,使用最大位置构建 COlumns 和 Pivot Value。

DECLARE @tv_Table1 TABLE 
                   (
                       Name VARCHAR(MAX),
                       [Values] VARCHAR(MAX)
                   );

INSERT INTO @tv_Table1 
VALUES ('Number1', '00,1110801100543 20190801103956,123456798708/01/201910:07:13,123456787608/01/201910:08:26,123456776508/01/201910:08:56,123456755508/01/201910:09:26,SOME TEXT1,123456744408/01/201910:29:08,'),
       ('Number2', 'val,val2,val3,'),
       ('Number3', 'val5,val7,val9,val14,'),
       ('Number4', '00,8980801100904 20190801128781,777333489808/01/201910:07:13,777333489808/01/201910:08:26,777333489808/01/201910:08:56,777333489808/01/201910:09:26,777333489808/01/201910:10:14,SOME TEXT2,777333489808/01/201910:05:56,777333489808/01/201910:12:05,777333489808/01/201910:12:44,777333489808/01/201910:13:02,777333489808/01/201910:13:02,777333489808/01/201910:13:32,777333489808/01/201910:14:03,777333489808/01/201910:15:15,777333489808/01/201910:15:46,SOME TEXT2,777333489808/01/201910:19:08,777333489808/01/201910:19:38,SOME TEXT2,777333489808/01/201910:28:31,777333489808/01/201910:29:02,777333489808/01/201910:29:32,777333489808/01/201910:30:02,777333489808/01/201910:30:32,777333489808/01/201910:31:02,777333489808/01/201910:31:32,777333489808/01/201910:32:02,777333489808/01/201910:32:33,777333489808/01/201910:33:02,777333489808/01/201910:33:33,777333489808/01/201910:34:11,SOME TEXT2,777333489808/01/201910:36:47,777333489808/01/201910:37:17,777333489808/01/201910:37:35,')

IF OBJECT_ID('tempdb..#RawData') IS NOT NULL
DROP TABLE #RawData;
CREATE TABLE #RawData(
    Name varchar(max) COLLATE DATABASE_DEFAULT not null,
    PositionInList int not null,
    [Value] varchar(max) COLLATE DATABASE_DEFAULT not null
)

;WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A, L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A, L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),
Numbers AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS n FROM L3)
INSERT #RawData(Name,PositionInList,[Value])
SELECT Name,
        ROW_NUMBER() OVER (PARTITION BY Name ORDER BY nums.n) AS PositionInList,
        LTRIM(RTRIM(SUBSTRING(valueTable.[Values], nums.n, charindex(N',', valueTable.[Values] + N',', nums.n) - nums.n))) AS [Value]
FROM   Numbers AS nums 
INNER JOIN @tv_Table1 AS valueTable 
ON nums.n <= CONVERT(int, LEN(valueTable.[Values])) 
AND SUBSTRING(N',' + valueTable.[Values], n, 1) = N','

-- SELECT * FROM #RawData;

DECLARE @MaxPos int;
DECLARE @Cols varchar(2048)='';
DECLARE @PvtVals varchar(2048)='';
DECLARE @i int=1;
DECLARE @SQL nvarchar(max)='';
SELECT @MaxPos = MAX(PositionInList) FROM #RawData;
WHILE @i<@MaxPos
BEGIN
    SET @Cols=@Cols+',['+CAST(@i AS varchar(20))+'] AS Column' + CAST(@i AS varchar(20))
    SET @PvtVals=@PvtVals+',['+CAST(@i AS varchar(20))+']'
    SET @i=@i+1;
END
SET @PvtVals=RIGHT(@PvtVals,LEN(@PvtVals)-1);

SET @SQL='
SELECT 
Name'+ @Cols +'
FROM #RawData
PIVOT
(
MAX([VALUE]) FOR PositionInList IN ('+@PvtVals+')
) AS Table2';
-- PRINT @SQL;
exec sp_executesql @SQL;