使用 unpivot 将未知数量的列转换为行的动态 SQL 查询
Posted
技术标签:
【中文标题】使用 unpivot 将未知数量的列转换为行的动态 SQL 查询【英文标题】:Dynamic SQL query to transpose unknown number of columns to rows using unpivot 【发布时间】:2018-09-03 05:18:10 【问题描述】:我有一张表,其中包含未知数量的任务,如下所示:
+----------+--------+--------+--------+--------+--------+
| CourseID | Task 1 | Task 2 | Task 3 | Task 4 | Task 5 |
+----------+--------+--------+--------+--------+--------+
| EN01 | 15 | 20 | 15 | 25 | 30 |
+----------+--------+--------+--------+--------+--------+
有时有 5 个任务,有时还有更多。如何使用 pivot 编写动态转置查询以获得此结果,而无需特别指定列标题,而不是列标题就像'Task%'
+----------+-------------+------------+
| CourseID | Task Number | Task Total |
+----------+-------------+------------+
| EN01 | Task 1 | 15 |
| EN01 | Task 2 | 20 |
| EN01 | Task 3 | 15 |
| EN01 | Task 4 | 25 |
| EN01 | Task 5 | 30 |
+----------+-------------+------------+
编辑:基本上我需要这个相反的:Efficiently convert rows to columns in sql server
我可以手动完成:
-- Unpivot the table.
SELECT [Class], TaskNumber, TaskTotal
FROM
(SELECT [Class], [Task 1], [Task 2], [Task 3], [Task 4]
FROM [Modules].[dbo].[2018-12ah] where [Given] like '%J:%') p
UNPIVOT
(TaskTotal FOR TaskNumber IN
([Task 1], [Task 2], [Task 3], [Task 4])
)AS Unpivot;
GO
我不知道该怎么做的下一步是动态构建任务 1 到 Z,因此我不必在查询中指定它们。
谢谢
【问题讨论】:
这是非常糟糕的数据库设计。如果可能的话,请将您的设计转换为以后的设计。 dba.stackexchange.com/questions/158461/… mssqltips.com/sqlservertip/3002/… @AnkitBajpai - 我明白,我听到你的声音。我们计划淘汰这个已有 10 年历史的旧系统并正确执行,但是我现在仍然需要支持它,这很不幸。 尝试上面显示的任何一个链接,都应该达到你所需要的 Sql Server 要求实际表具有固定 列数。编写 unpivot 查询运行,就好像 all 它们总是被填充,然后添加where
条件以过滤掉 null
s。
【参考方案1】:
您可以将INFORMATION_SCHEMA
中的信息与动态 TSQL 一起使用,但请注意安全隐患(即 SQL 注入):
create table [dbo].[Test] ([CourseID] nvarchar(50), [Task 1] int, [Task 2] int, [Task 3] int, [Task 4] int, [Task 5] int)
insert into [dbo].[Test] select 'EN01', 15,20,15,25,30
declare @sql nvarchar(max) = ''
declare @cols nvarchar(max) = ''
select @cols = @cols +','+ QUOTENAME(COLUMN_NAME)
from INFORMATION_SCHEMA.COLUMNS
where TABLE_SCHEMA='dbo' and TABLE_NAME='test' and COLUMN_NAME like 'Task%'
order by ORDINAL_POSITION
set @cols = substring(@cols, 2, LEN(@cols))
set @sql = @sql + ' select u.[CourseID], u.[TaskNumber], u.[TaskTotal] '
set @sql = @sql + ' from [dbo].[Test] s '
set @sql = @sql + ' unpivot '
set @sql = @sql + ' ( '
set @sql = @sql + ' [TaskTotal] '
set @sql = @sql + ' for [TaskNumber] in ('+ @cols + ') '
set @sql = @sql + ' ) u;'
execute(@sql)
5 个任务的结果:
如果您更改表结构添加新任务列:
DROP TABLE [dbo].[Test]
create table [dbo].[Test] ([CourseID] nvarchar(50), [Task 1] int, [Task 2] int, [Task 3] int, [Task 4] int, [Task 5] int, [Task 6] int)
insert into [dbo].[Test] select 'EN01', 15,20,15,25,30,1000
相同的查询将返回此结果集:
如果您更喜欢 CROSS APPLY
方法来取消透视,这里是一个替代解决方案:
create table [dbo].[Test] ([CourseID] nvarchar(50), [Task 1] int, [Task 2] int, [Task 3] int, [Task 4] int, [Task 5] int)
insert into [dbo].[Test] select 'EN01', 15,20,15,25,30
declare @sql nvarchar(max) = ''
declare @values nvarchar(max) = ''
select @values = @values +',(''' + COLUMN_NAME + ''','+ QUOTENAME(COLUMN_NAME) + ')'
from INFORMATION_SCHEMA.COLUMNS
where TABLE_SCHEMA='dbo' and TABLE_NAME='test' and COLUMN_NAME like 'Task%'
order by ORDINAL_POSITION
set @values = substring(@values, 2, LEN(@values))
set @sql = @sql + ' Select [CourseID], B.* '
set @sql = @sql + ' From [dbo].[Test] '
set @sql = @sql + ' Cross Apply (values '
set @sql = @sql + @values
set @sql = @sql + ' ) B(TaskNumber, TaskTotal) '
execute(@sql)
【讨论】:
感谢您提供详细信息。我能够在查询 information_schema 表以查找列时取得类似的进展。然后我将查询添加到视图中,并意识到视图中不能有变量。所以我回到了如何在视图中表示这一点的绘图板上。你有什么建议?以上是关于使用 unpivot 将未知数量的列转换为行的动态 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章