使用 SQL 反透视多列
Unpivot Multiple Columns with SQL
需要对列出期权和期权价格的多个列进行逆透视。可以使用以下代码创建示例起始数据集:
CREATE TABLE testtable (
ID int,
OptionA nvarchar(25),
OptionACost decimal(16,4),
OptionB nvarchar(25),
OptionBCost decimal(16,4),
OptionC nvarchar(25),
OptionCCost decimal(16,4) )
INSERT INTO testtable (ID, OptionA, OptionACost, OptionB, OptionBCost,
OptionC, OptionCCost)
VALUES
('1', 'Red Paint', '11.98', 'Leather Trim', '20.00', 'Matte Finish', '5.66'),
('2', 'Blue Paint', '13.48', 'Suede Trim', '16.00', 'Gloss Finish', '3.82'),
('3', 'Black Paint', '10.00', 'Leather Trim', '20.00', 'Matte Finish', '5.66'),
('4', 'Red Paint', '11.98', 'No Trim', '0.00', 'Matte Finish', '5.66');
我理想的结果示例数据集是使用以下代码创建的:
CREATE TABLE testtableresult (
ID int,
OptionName nvarchar(25),
OptionCost decimal(16,4))
INSERT INTO testtable2 (ID, OptionName, OptionCost)
VALUES
('1', 'Red Paint', '11.98'),
('2', 'Blue Paint', '13.48'),
('3', 'Black Paint', '10.00'),
('4', 'Red Paint', '11.98')
('1', 'Leather Trim', '20.00'),
('2', 'Suede Trim', '16.00'),
('3', 'Leather Trim', '20.00'),
('4', 'No Trim', '0.00')
('1', 'Matte Finish', '5.66'),
('2', 'Suede Trim', '3.88'),
('3', 'Matte Finish', '5.66'),
('4', 'Matte Finish', '5.66');
SELECT ID, OptionA AS OptionName,
CAST(OptionACost as decimal(10,2)) AS OptionCost
FROM testtable
UNION ALL
SELECT ID, OptionB AS OptionName,
CAST(OptionBCost as decimal(10,2)) AS OptionCost
FROM testtable
UNION ALL
SELECT ID, OptionC AS OptionName,
CAST(OptionCCost as decimal(10,2)) AS OptionCost
FROM testtable
GO
ID | OptionName | OptionCost
-: | :----------- | :---------
1 | Red Paint | 11.98
2 | Blue Paint | 13.48
3 | Black Paint | 10.00
4 | Red Paint | 11.98
1 | Leather Trim | 20.00
2 | Suede Trim | 16.00
3 | Leather Trim | 20.00
4 | No Trim | 0.00
1 | Matte Finish | 5.66
2 | Gloss Finish | 3.82
3 | Matte Finish | 5.66
4 | Matte Finish | 5.66
dbfiddle here
SQL 服务器有 APPLY
运算符(即 CROSS APPLY
),它可以像 UNPIVOT
那样做你想要的事情
SELECT a.* FROM #testtable t
CROSS APPLY (
VALUES (ID, OptionA, OptionACost, 1), (ID,OptionB, OptionBCost, 2),
(ID, OptionC, OptionCCost, 3)
)a(Id, Name, Cost, ids)
ORDER BY a.ids, a.id
除了Yogesh's解决方案,这里是动态创建多个CROSS APPLY
的代码
注意
主要假设是您在测试表中的列称为 "Option<>"
和 "Option<>Cost" 以便正确创建分组
SET NOCOUNT ON
IF OBJECT_ID ('tempdb..#Cols') IS NOT NULL DROP TABLE #Cols
;WITH Cols as
(
SELECT COLUMN_NAME , FIRST_VALUE(COLUMN_NAME) OVER (ORDER BY ORDINAL_POSITION ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) MainCol
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'testtable'
)
SELECT *
INTO #Cols
FROM Cols
WHERE COLUMN_NAME !=MainCol
DECLARE @ColsOptions NVARCHAR(MAX)= ''
DECLARE @Sql NVARCHAR(MAX)= ''
;WITH Pvt as
(
SELECT *,
DENSE_RANK() OVER (PARTITION BY MainCol ORDER BY SUBSTRING(COLUMN_NAME,LEN('Option')+1, 1)) Grp
FROM #Cols
)
, InternalPvt AS
(
SELECT * , ROW_NUMBER () OVER (PARTITION BY Grp ORDER BY Grp) InternalGrp
FROM Pvt
)
, CrossApply as
(
SELECT MAX(MainCol) MainCol,
MAX(CASE WHEN InternalGrp = 1 THEN COLUMN_NAME END) [Option],
MAX(CASE WHEN InternalGrp = 2 THEN COLUMN_NAME END) OptionCost,
Grp
FROM InternalPvt
GROUP BY Grp
)
SELECT @ColsOptions += '('+MainCol+','+[Option]+','+OptionCost+','+CONVERT(NVARCHAR(10),Grp) +'),'+CHAR(10)
FROM CrossApply
SET @ColsOptions = SUBSTRING(@ColsOptions,0,LEN(@ColsOptions) - 1 )
SET @sql =
'SELECT a.*
FROM testtable t
CROSS APPLY (
VALUES '+@ColsOptions+'
) a(Id, Name, Cost, ids)
ORDER BY a.ids, a.id'
exec sp_executesql @Sql
需要对列出期权和期权价格的多个列进行逆透视。可以使用以下代码创建示例起始数据集:
CREATE TABLE testtable (
ID int,
OptionA nvarchar(25),
OptionACost decimal(16,4),
OptionB nvarchar(25),
OptionBCost decimal(16,4),
OptionC nvarchar(25),
OptionCCost decimal(16,4) )
INSERT INTO testtable (ID, OptionA, OptionACost, OptionB, OptionBCost,
OptionC, OptionCCost)
VALUES
('1', 'Red Paint', '11.98', 'Leather Trim', '20.00', 'Matte Finish', '5.66'),
('2', 'Blue Paint', '13.48', 'Suede Trim', '16.00', 'Gloss Finish', '3.82'),
('3', 'Black Paint', '10.00', 'Leather Trim', '20.00', 'Matte Finish', '5.66'),
('4', 'Red Paint', '11.98', 'No Trim', '0.00', 'Matte Finish', '5.66');
我理想的结果示例数据集是使用以下代码创建的:
CREATE TABLE testtableresult (
ID int,
OptionName nvarchar(25),
OptionCost decimal(16,4))
INSERT INTO testtable2 (ID, OptionName, OptionCost)
VALUES
('1', 'Red Paint', '11.98'),
('2', 'Blue Paint', '13.48'),
('3', 'Black Paint', '10.00'),
('4', 'Red Paint', '11.98')
('1', 'Leather Trim', '20.00'),
('2', 'Suede Trim', '16.00'),
('3', 'Leather Trim', '20.00'),
('4', 'No Trim', '0.00')
('1', 'Matte Finish', '5.66'),
('2', 'Suede Trim', '3.88'),
('3', 'Matte Finish', '5.66'),
('4', 'Matte Finish', '5.66');
SELECT ID, OptionA AS OptionName, CAST(OptionACost as decimal(10,2)) AS OptionCost FROM testtable UNION ALL SELECT ID, OptionB AS OptionName, CAST(OptionBCost as decimal(10,2)) AS OptionCost FROM testtable UNION ALL SELECT ID, OptionC AS OptionName, CAST(OptionCCost as decimal(10,2)) AS OptionCost FROM testtable GO
ID | OptionName | OptionCost -: | :----------- | :--------- 1 | Red Paint | 11.98 2 | Blue Paint | 13.48 3 | Black Paint | 10.00 4 | Red Paint | 11.98 1 | Leather Trim | 20.00 2 | Suede Trim | 16.00 3 | Leather Trim | 20.00 4 | No Trim | 0.00 1 | Matte Finish | 5.66 2 | Gloss Finish | 3.82 3 | Matte Finish | 5.66 4 | Matte Finish | 5.66
dbfiddle here
SQL 服务器有 APPLY
运算符(即 CROSS APPLY
),它可以像 UNPIVOT
SELECT a.* FROM #testtable t
CROSS APPLY (
VALUES (ID, OptionA, OptionACost, 1), (ID,OptionB, OptionBCost, 2),
(ID, OptionC, OptionCCost, 3)
)a(Id, Name, Cost, ids)
ORDER BY a.ids, a.id
除了Yogesh's解决方案,这里是动态创建多个CROSS APPLY
注意 主要假设是您在测试表中的列称为 "Option<>" 和 "Option<>Cost" 以便正确创建分组
SET NOCOUNT ON
IF OBJECT_ID ('tempdb..#Cols') IS NOT NULL DROP TABLE #Cols
;WITH Cols as
(
SELECT COLUMN_NAME , FIRST_VALUE(COLUMN_NAME) OVER (ORDER BY ORDINAL_POSITION ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) MainCol
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'testtable'
)
SELECT *
INTO #Cols
FROM Cols
WHERE COLUMN_NAME !=MainCol
DECLARE @ColsOptions NVARCHAR(MAX)= ''
DECLARE @Sql NVARCHAR(MAX)= ''
;WITH Pvt as
(
SELECT *,
DENSE_RANK() OVER (PARTITION BY MainCol ORDER BY SUBSTRING(COLUMN_NAME,LEN('Option')+1, 1)) Grp
FROM #Cols
)
, InternalPvt AS
(
SELECT * , ROW_NUMBER () OVER (PARTITION BY Grp ORDER BY Grp) InternalGrp
FROM Pvt
)
, CrossApply as
(
SELECT MAX(MainCol) MainCol,
MAX(CASE WHEN InternalGrp = 1 THEN COLUMN_NAME END) [Option],
MAX(CASE WHEN InternalGrp = 2 THEN COLUMN_NAME END) OptionCost,
Grp
FROM InternalPvt
GROUP BY Grp
)
SELECT @ColsOptions += '('+MainCol+','+[Option]+','+OptionCost+','+CONVERT(NVARCHAR(10),Grp) +'),'+CHAR(10)
FROM CrossApply
SET @ColsOptions = SUBSTRING(@ColsOptions,0,LEN(@ColsOptions) - 1 )
SET @sql =
'SELECT a.*
FROM testtable t
CROSS APPLY (
VALUES '+@ColsOptions+'
) a(Id, Name, Cost, ids)
ORDER BY a.ids, a.id'
exec sp_executesql @Sql