使用 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 条件以过滤掉 nulls。 【参考方案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 查询的主要内容,如果未能解决你的问题,请参考以下文章

SQL 查询将列转换为行

没有聚合的 sql server 中的 UNPIVOT

将列转置为行 SQL Server

SQL:在 Chartio 中动态地将列转换为行

在不确定数量的列上进行 UNPIVOT

红移。我们如何(动态地)将表从列转置为行?