SQL 服务器如何将管道分隔的列字符串更改为行
SQL server how to change pipe separated column string to rows
我在 table 中有 3 列,其中 2 列的字符串由“|”分隔管道。这两个列值相互依赖。
例如:我在 table 中有这样的数据:
ID product quantity
1 A|B|C 1|2|3
2 X|Y|Z 7|8|9
我想把它改成这样:
ID product quantity
1 A 1
1 B 2
1 C 3
2 X 7
2 Y 8
2 Z 9
因为我正在使用 SSMS,所以除了 SQL,我别无选择。我尝试使用交叉应用,但我没有得到正确的结果。对于 1 行,我收到 9 行而不是 3 行。
谁能建议我应该使用哪种方法?
提前致谢!!
杰克
这相当棘手,因为您需要使值匹配。以下采用递归 CTE 方法:
with cte as (
select id,
left(product, charindex('|', product + '|') - 1) as product,
left(quantity, charindex('|', quantity + '|') - 1) as quantity,
substring(product, charindex('|', product + '|') + 1, 1000) as products,
substring(quantity, charindex('|', quantity + '|') + 1, 1000) as quantities
from t
union all
select id,
left(products, charindex('|', products + '|') - 1) as product,
left(quantities, charindex('|', quantities + '|') - 1) as quantity,
substring(products, charindex('|', products + '|') + 1, 1000) as products,
substring(quantities, charindex('|', quantities + '|') + 1, 1000) as quantities
from cte
where products <> '' and quantities <> ''
)
select id, product, quantity
from cte;
这里有点Rextester.
测试数据
CREATE TABLE #t (ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #t VALUES
(1 ,'A|B|C' , '1|2|3'),
(2 ,'X|Y|Z' , '7|8|9');
查询
WITH Products AS (
SELECT ID
, Product_Split.a.value('.', 'VARCHAR(100)') Products
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
FROM (
SELECT ID
,Cast ('<X>'
+ Replace(product, '|', '</X><X>')
+ '</X>' AS XML) AS Product_Data
FROM #t
) AS t
CROSS APPLY Product_Data.nodes ('/X') AS Product_Split(a)
),
Quantities AS (
SELECT ID
, Quantity_Split.a.value('.', 'VARCHAR(100)') Quantity
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
FROM (
SELECT ID
,Cast ('<X>'
+ Replace(quantity, '|', '</X><X>')
+ '</X>' AS XML) AS Quantity_Data
FROM #t
) AS t
CROSS APPLY Quantity_Data.nodes ('/X') AS Quantity_Split(a)
)
SELECT t.ID
, P.Products
, Q.Quantity
FROM #t t
LEFT JOIN Products P ON t.ID = p.ID
LEFT JOIN Quantities Q ON Q.ID = t.ID
AND Q.rn = p.rn
结果集
╔════╦══════════╦══════════╗
║ ID ║ Products ║ Quantity ║
╠════╬══════════╬══════════╣
║ 1 ║ A ║ 1 ║
║ 1 ║ B ║ 2 ║
║ 1 ║ C ║ 3 ║
║ 2 ║ X ║ 7 ║
║ 2 ║ Y ║ 8 ║
║ 2 ║ Z ║ 9 ║
╚════╩══════════╩══════════╝
拆分字符串很容易,有很多例子。这里棘手的部分是通过它们的位置连接片段。我的建议是使用 XML 的能力来按位置定位元素:
DECLARE @tbl TABLE(ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO @tbl VALUES
(1 ,'A|B|C' , '1|2|3')
,(2 ,'X|Y|Z' , '7|8|9');
--这是查询
WITH CastedToXML AS
(
SELECT *
,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
FROM @tbl
)
SELECT *
,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) AS Tally(Nmbr);
结果
+----+------+-------------------+--------------------+
| ID | Nmbr | ProductAtPosition | QuantityAtPosition |
+----+------+-------------------+--------------------+
| 1 | 1 | A | 1 |
+----+------+-------------------+--------------------+
| 1 | 2 | B | 2 |
+----+------+-------------------+--------------------+
| 1 | 3 | C | 3 |
+----+------+-------------------+--------------------+
| 2 | 1 | X | 7 |
+----+------+-------------------+--------------------+
| 2 | 2 | Y | 8 |
+----+------+-------------------+--------------------+
| 2 | 3 | Z | 9 |
+----+------+-------------------+--------------------+
一些解释:
演员 xml 将您的 A|B|C
转移到
<x>A</x>
<x>B</x>
<x>C</x>
此列表与使用 <x>
的计数作为 TOP
限制 动态创建的数字集相结合 。
现在可以很容易地从你的 XML 中按位置选出 <x>
。
试试吧!
更新:非唯一 ID
DECLARE @tbl TABLE(ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO @tbl VALUES
(1 ,'A|B|C' , '1|2|3')
,(2 ,'X|Y|Z' , '7|8|9')
,(3 ,'a|b|c' , '7|8|9')
,(2 ,'D|e|f' , '7|8|9')
;
--This is the query
WITH CastedToXML AS
(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID) AS RowIndex
,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
FROM @tbl
)
SELECT *
,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) AS Tally(Nmbr);
我在 table 中有 3 列,其中 2 列的字符串由“|”分隔管道。这两个列值相互依赖。
例如:我在 table 中有这样的数据:
ID product quantity
1 A|B|C 1|2|3
2 X|Y|Z 7|8|9
我想把它改成这样:
ID product quantity
1 A 1
1 B 2
1 C 3
2 X 7
2 Y 8
2 Z 9
因为我正在使用 SSMS,所以除了 SQL,我别无选择。我尝试使用交叉应用,但我没有得到正确的结果。对于 1 行,我收到 9 行而不是 3 行。 谁能建议我应该使用哪种方法?
提前致谢!! 杰克
这相当棘手,因为您需要使值匹配。以下采用递归 CTE 方法:
with cte as (
select id,
left(product, charindex('|', product + '|') - 1) as product,
left(quantity, charindex('|', quantity + '|') - 1) as quantity,
substring(product, charindex('|', product + '|') + 1, 1000) as products,
substring(quantity, charindex('|', quantity + '|') + 1, 1000) as quantities
from t
union all
select id,
left(products, charindex('|', products + '|') - 1) as product,
left(quantities, charindex('|', quantities + '|') - 1) as quantity,
substring(products, charindex('|', products + '|') + 1, 1000) as products,
substring(quantities, charindex('|', quantities + '|') + 1, 1000) as quantities
from cte
where products <> '' and quantities <> ''
)
select id, product, quantity
from cte;
这里有点Rextester.
测试数据
CREATE TABLE #t (ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #t VALUES
(1 ,'A|B|C' , '1|2|3'),
(2 ,'X|Y|Z' , '7|8|9');
查询
WITH Products AS (
SELECT ID
, Product_Split.a.value('.', 'VARCHAR(100)') Products
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
FROM (
SELECT ID
,Cast ('<X>'
+ Replace(product, '|', '</X><X>')
+ '</X>' AS XML) AS Product_Data
FROM #t
) AS t
CROSS APPLY Product_Data.nodes ('/X') AS Product_Split(a)
),
Quantities AS (
SELECT ID
, Quantity_Split.a.value('.', 'VARCHAR(100)') Quantity
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
FROM (
SELECT ID
,Cast ('<X>'
+ Replace(quantity, '|', '</X><X>')
+ '</X>' AS XML) AS Quantity_Data
FROM #t
) AS t
CROSS APPLY Quantity_Data.nodes ('/X') AS Quantity_Split(a)
)
SELECT t.ID
, P.Products
, Q.Quantity
FROM #t t
LEFT JOIN Products P ON t.ID = p.ID
LEFT JOIN Quantities Q ON Q.ID = t.ID
AND Q.rn = p.rn
结果集
╔════╦══════════╦══════════╗
║ ID ║ Products ║ Quantity ║
╠════╬══════════╬══════════╣
║ 1 ║ A ║ 1 ║
║ 1 ║ B ║ 2 ║
║ 1 ║ C ║ 3 ║
║ 2 ║ X ║ 7 ║
║ 2 ║ Y ║ 8 ║
║ 2 ║ Z ║ 9 ║
╚════╩══════════╩══════════╝
拆分字符串很容易,有很多例子。这里棘手的部分是通过它们的位置连接片段。我的建议是使用 XML 的能力来按位置定位元素:
DECLARE @tbl TABLE(ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO @tbl VALUES
(1 ,'A|B|C' , '1|2|3')
,(2 ,'X|Y|Z' , '7|8|9');
--这是查询
WITH CastedToXML AS
(
SELECT *
,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
FROM @tbl
)
SELECT *
,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) AS Tally(Nmbr);
结果
+----+------+-------------------+--------------------+
| ID | Nmbr | ProductAtPosition | QuantityAtPosition |
+----+------+-------------------+--------------------+
| 1 | 1 | A | 1 |
+----+------+-------------------+--------------------+
| 1 | 2 | B | 2 |
+----+------+-------------------+--------------------+
| 1 | 3 | C | 3 |
+----+------+-------------------+--------------------+
| 2 | 1 | X | 7 |
+----+------+-------------------+--------------------+
| 2 | 2 | Y | 8 |
+----+------+-------------------+--------------------+
| 2 | 3 | Z | 9 |
+----+------+-------------------+--------------------+
一些解释:
演员 xml 将您的 A|B|C
转移到
<x>A</x>
<x>B</x>
<x>C</x>
此列表与使用 <x>
的计数作为 TOP
限制 动态创建的数字集相结合 。
现在可以很容易地从你的 XML 中按位置选出 <x>
。
试试吧!
更新:非唯一 ID
DECLARE @tbl TABLE(ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO @tbl VALUES
(1 ,'A|B|C' , '1|2|3')
,(2 ,'X|Y|Z' , '7|8|9')
,(3 ,'a|b|c' , '7|8|9')
,(2 ,'D|e|f' , '7|8|9')
;
--This is the query
WITH CastedToXML AS
(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID) AS RowIndex
,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
FROM @tbl
)
SELECT *
,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) AS Tally(Nmbr);