根据分隔符将值拆分为行

时间:2016-10-21 10:53:56

标签: sql sql-server sql-server-2008

将表中的给定输入值拆分为下一行(需要在第3行之后完全拆分)

输入

Temp (column/Table1)
TBL101 | PC | 1.00 | COMP101 | CS | 1.00 | TQR101 | CP | 5.00 | TXL101 | PC | 1.00 | SQL101 | PC | 1.00........etc

输出

 ID          Pack    qty (columns/Table2)

TBL101       PC      1.00
COMP101      CS      1.00
TQR101       CP      5.00
TXL101       PC      1.00
SQL101       PC      1.00

我使用以下代码执行此操作,但仅适用于第一行正常工作

DECLARE @Delimiter VARCHAR(40)
SET @Delimiter = '|'
;WITH CTE AS
(
    SELECT
        CAST('<M>' + REPLACE(temp, @Delimiter , '</M><M>') + '</M>' AS XML)
        AS [ColName XML]
    FROM Table1
)
--INSERT INTO Table2
  -- (ID,PACK,OrderQty)
SELECT
 [ColName XML].value('/M[1]', 'bigint') As [ID],
 [ColName XML].value('/M[2]', 'VARCHAR(40)') As [Pack},
 [ColName XML].value('/M[3]', 'decimal(18,2)') As [OrderQty]

FROM CTE
GO

2 个答案:

答案 0 :(得分:1)

更新:字符串中动态字段数的版本。如果您想在评论中提出一些问题,请在此处解释。享受。

CREATE TABLE #dta(
    r NVARCHAR(4000) NOT NULL
);

INSERT INTO #dta(r)VALUES
    ('TBL101 | PC | 1.00 | COMP101 | CS | 1.00 | TQR101 | CP | 5.00 | TXL101 | PC | 1.00 | SQL101 | PC | 1.00'),
    ('TBL102 | PC | 4.00 | COMP102 | CS | 3.00 | TQR102 | CP | 6.00 | TXL102 | PC | 7.00 | SQL102 | PC | 9.00');

DECLARE @num_fields INT;

SELECT
    @num_fields=MAX(LEN(r) - LEN(REPLACE(r,'|',''))) + 1
FROM
    #dta;

DECLARE @fields_sel NVARCHAR(MAX);

SET @fields_sel=STUFF((
SELECT 
 ',[ColName XML].value(''/M['+CAST((N-1)*3+1 AS VARCHAR)+']'', ''NVARCHAR(40)'') As [ID'+CAST(N AS VARCHAR)+']'+
 ',[ColName XML].value(''/M['+CAST((N-1)*3+2 AS VARCHAR)+']'', ''NVARCHAR(40)'') As [Pack'+CAST(N AS VARCHAR)+']'+
 ',[ColName XML].value(''/M['+CAST((N-1)*3+3 AS VARCHAR)+']'', ''decimal(18,2)'') As [OrderQty'+CAST(N AS VARCHAR)+']'
FROM (
    -- 1000 rows
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
) AS tally(N)
WHERE N<=@num_fields/3
FOR XML PATH('')),1,1,'');

DECLARE @ca_sel NVARCHAR(MAX);

SET @ca_sel=STUFF((
SELECT
    ' UNION ALL SELECT RTRIM(LTRIM([ID'+CAST(N AS VARCHAR)+'])),RTRIM(LTRIM([Pack'+CAST(N AS VARCHAR)+'])),[OrderQty'+CAST(N AS VARCHAR)+']'
FROM (
    -- 1000 rows
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
) AS tally(N)
WHERE N<=@num_fields/3
FOR XML PATH('')),1,LEN(' UNION ALL'),'');

--SELECT @ca_sel;

--SELECT @fields_sel;

DECLARE @Delimiter NVARCHAR(40)
SET @Delimiter = N'|'

DECLARE @sql NVARCHAR(MAX);
SET @sql=N'
;WITH CTE([ColName XML]) AS
(
    SELECT
        CAST(''<M>'' + REPLACE(r, @Delimiter , ''</M><M>'') + ''</M>'' AS XML) AS [ColName XML]
    FROM 
        #dta
), sep_fields AS (
SELECT
    '+@fields_sel+N'
FROM CTE
)
SELECT
    up.*
FROM
    sep_fields
    CROSS APPLY (
        '+@ca_sel+N'
    ) AS up([ID],[Pack],[OrderQty])';

---SELECT @sql;

EXEC sp_executesql @sql, N'@Delimiter NVARCHAR(40)', @Delimiter;

DROP TABLE #dta;

这里有一个答案,当你事先知道字符串字段中会出现多少字段时(例如在你的问题中,15):

CREATE TABLE #dta(
    r NVARCHAR(4000) NOT NULL
);

INSERT INTO #dta(r)VALUES
    ('TBL101 | PC | 1.00 | COMP101 | CS | 1.00 | TQR101 | CP | 5.00 | TXL101 | PC | 1.00 | SQL101 | PC | 1.00'),
    ('TBL102 | PC | 4.00 | COMP102 | CS | 3.00 | TQR102 | CP | 6.00 | TXL102 | PC | 7.00 | SQL102 | PC | 9.00');

DECLARE @Delimiter NVARCHAR(40)
SET @Delimiter = N'|'
;WITH CTE([ColName XML]) AS
(
    SELECT
        CAST('<M>' + REPLACE(r, @Delimiter , '</M><M>') + '</M>' AS XML) AS [ColName XML]
    FROM 
        #dta
), sep_fields AS (
SELECT
 [ColName XML].value('/M[1]', 'NVARCHAR(40)') As [ID1],
 [ColName XML].value('/M[2]', 'NVARCHAR(40)') As [Pack1],
 [ColName XML].value('/M[3]', 'decimal(18,2)') As [OrderQty1],
 [ColName XML].value('/M[4]', 'NVARCHAR(40)') As [ID2],
 [ColName XML].value('/M[5]', 'NVARCHAR(40)') As [Pack2],
 [ColName XML].value('/M[6]', 'decimal(18,2)') As [OrderQty2],
 [ColName XML].value('/M[7]', 'NVARCHAR(40)') As [ID3],
 [ColName XML].value('/M[8]', 'NVARCHAR(40)') As [Pack3],
 [ColName XML].value('/M[9]', 'decimal(18,2)') As [OrderQty3],
 [ColName XML].value('/M[10]', 'NVARCHAR(40)') As [ID4],
 [ColName XML].value('/M[11]', 'NVARCHAR(40)') As [Pack4],
 [ColName XML].value('/M[12]', 'decimal(18,2)') As [OrderQty4],
 [ColName XML].value('/M[13]', 'NVARCHAR(40)') As [ID5],
 [ColName XML].value('/M[14]', 'NVARCHAR(40)') As [Pack5],
 [ColName XML].value('/M[15]', 'decimal(18,2)') As [OrderQty5]
FROM CTE
)
SELECT
    up.*
FROM
    sep_fields
    CROSS APPLY (
        SELECT [ID1],[Pack1],[OrderQty1]
        UNION ALL
        SELECT [ID2],[Pack2],[OrderQty2]
        UNION ALL
        SELECT [ID3],[Pack3],[OrderQty3]
        UNION ALL
        SELECT [ID4],[Pack4],[OrderQty4]
        UNION ALL
        SELECT [ID5],[Pack5],[OrderQty5]
    ) AS up

DROP TABLE #dta;

结果是:

╔═══════════╦═══════╦═══════════╗
║    ID1    ║ Pack1 ║ OrderQty1 ║
╠═══════════╬═══════╬═══════════╣
║ TBL101    ║  PC   ║ 1.00      ║
║  COMP101  ║  CS   ║ 1.00      ║
║  TQR101   ║  CP   ║ 5.00      ║
║  TXL101   ║  PC   ║ 1.00      ║
║  SQL101   ║  PC   ║ 1.00      ║
║ TBL102    ║  PC   ║ 4.00      ║
║  COMP102  ║  CS   ║ 3.00      ║
║  TQR102   ║  CP   ║ 6.00      ║
║  TXL102   ║  PC   ║ 7.00      ║
║  SQL102   ║  PC   ║ 9.00      ║
╚═══════════╩═══════╩═══════════╝

答案 1 :(得分:1)

您可以使用:

DECLARE @str nvarchar(max) = N'TBL101 | PC | 1.00 | COMP101 | CS | 1.00 | TQR101 | CP | 5.00 | TXL101 | PC | 1.00 | SQL101 | PC | 1.00',
        @x xml

SELECT @x = CAST('<a>'+REPLACE(@str,' | ', '</a><a>')+'</a>' as xml)

;WITH cte AS (
SELECT  t.c.value('.','nvarchar(100)') as [values],
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as rn
FROM @x.nodes('/a') as t(c)
)

SELECT  [1] as ID1,
        [2] as Pack1,
        [0] as OrderQty1
FROM (
    SELECT  rn- CASE WHEN rn%3 = 0 THEN 3 ELSE rn%3 END as seq,
            rn%3 as s,
            [values]
    FROM cte
) as t
PIVOT (
    MAX([VALUES]) FOR s IN ([1],[2],[0])
) as pvt

输出:

ID1     Pack1   OrderQty1
TBL101  PC      1.00
COMP101 CS      1.00
TQR101  CP      5.00
TXL101  PC      1.00
SQL101  PC      1.00

首先转换为简单的XML。然后使用ROW_NUMBER()添加SELECT NULL(关于此技巧阅读here),它会为每一行添加一些ID。然后我们使用行号来获得一些序列,因此我们可以调整结果。

修改

如果您正在使用table,那么将整个表格设为XML:

DECLARE @temptable TABLE (
    Column1 nvarchar(max)
)

INSERT INTO @temptable VALUES
(N'TBL101 | PC | 1.00 | COMP101 | CS | 1.00 | TQR101 | CP | 5.00 | TXL101 | PC | 1.00 | SQL101 | PC | 1.00')

DECLARE @x xml

SELECT @x = (
    SELECT CAST('<a>'+REPLACE(Column1,' | ', '</a><a>')+'</a>' as xml)
    FROM @temptable
    FOR XML PATH('')
)

然后如上所述参与CTE。