从3个表中透视2列并创建透视列名以避免冲突-SQL Server 2008R2 介绍与问题
在我的例子中,我有老师、学生和课程。我想了解一下,哪门课程是由谁在哪间教室里教授的,以及这门课程的所有学生。我已经运行了基本设置(带有一些手工编码的语句)。但直到现在,我还没有准备好正确的材料声明:从3个表中透视2列并创建透视列名以避免冲突-SQL Server 2008R2 介绍与问题,sql,sql-server-2008,pivot,Sql,Sql Server 2008,Pivot,在我的例子中,我有老师、学生和课程。我想了解一下,哪门课程是由谁在哪间教室里教授的,以及这门课程的所有学生。我已经运行了基本设置(带有一些手工编码的语句)。但直到现在,我还没有准备好正确的材料声明: 准备@colsstudens,这样我就可以把名字放在列标题中,不必再弄乱id(添加100),以免rooms.id和students.id之间发生冲突 准备@colsRooms,这样我就不必硬拼房间名了 通过使用EXEC sp_executesql@sql将i全部放在一起 您可以找到所有sql语句
- 准备
,这样我就可以把名字放在列标题中,不必再弄乱id(添加100),以免rooms.id和students.id之间发生冲突@colsstudens
- 准备
,这样我就不必硬拼房间名了@colsRooms
- 通过使用
EXEC sp_executesql@sql将i全部放在一起代码>
RoomName
和StudentName
,并使用列值作为新列名。所有用于创建表和数据的SQL语句都位于末尾
Id | Course | Teacher | A3 | E7 | Penny | Cooper | Koothrap. | Amy
---+--------+---------+----+----+-------+--------+-----------+-----+
1 | C# 1 | Marc G. | | 1 | 1 | | |
2 | C# 2 | Sam S. | | 1 | 1 | | 1 |
3 | C# 3 | John S. | 1 | | | 1 | |
4 | C# 3 | Reed C. | | 1 | | | 1 |
5 | SQL 1 | Marc G. | 1 | | | | |
6 | SQL 2 | Marc G. | 1 | | | | |
7 | SQL 3 | Marc G. | | 1 | | 1 | | 1
8 | SQL 3 | Gbn | 1 | | | | 1 |
到目前为止我所拥有的
缺少什么
上述声明是手工编写的。因为我事先不知道房间或学生,所以我需要动态地为房间和学生列创建Pivot语句。关于如何做到这一点,有很多例子。通常的做法是使用东西:
DECLARE @colsStudents AS NVARCHAR(MAX);
SET @colsStudents = STUFF(
(SELECT N',' + QUOTENAME(y) AS [text()] FROM
(SELECT DISTINCT 100 + Id AS y FROM dbo.Students) AS Y
ORDER BY y
FOR XML PATH('')
),1
,1
,N'');
Select @colsStudents
这将返回学生ID的[101]、[102]、[103]、[104]
。我为每个id添加了100,以避免students.id和teh rooms.id列之间的冲突
正如在介绍中提到的,我需要动态地创建这样的东西
[1] as RoomName_1, [2] as RoomName_1 -- RoomColumns
[1] as StudentName1, [2] as StudentName2, ... ,[4] as Amy -- StudentColumns
但是我对stuff语句的所有尝试都失败了
用于创建表和数据的所有SQL语句
您可以使用动态sql查询为两个数据透视列创建别名字符串, 例如,对于学生专栏:
DECLARE @colsStudents AS NVARCHAR(MAX),
@colsstudentalias AS NVARCHAR(MAX),
@colsRooms AS NVARCHAR(MAX),
@colsRoomsalias AS NVARCHAR(MAX)
SELECT @colsStudents = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(100 + Id)
FROM dbo.Students
FOR XML PATH('')
), 1, 1, ''
)
SELECT @colsstudentalias = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(100 + Id)
+ ' as ' + QUOTENAME(ltrim(rtrim(StudentName)))
FROM dbo.Students
FOR XML PATH('')
), 1, 1, ''
)
SELECT @colsRooms = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(Id)
FROM dbo.Rooms
FOR XML PATH('')
), 1, 1, ''
)
SELECT @colsRoomsalias = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(Id)
+ ' as ' + QUOTENAME(ltrim(rtrim(RoomName)))
FROM dbo.Rooms
FOR XML PATH('')
), 1, 1, ''
)
--SELECT @colsStudents, @colsstudentalias, @colsRooms, @colsRoomsalias
DECLARE @sql varchar(max)
set @sql = ';With PivotData as (
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,r.Id as RoomId, r.RoomName as RoomName
,100 + s.Id as StudentId, s.StudentName as Student
FROM CourseDetails cd
Left JOIN Courses c ON cd.CourseId = c.Id
Left JOIN Teachers t ON cd.TeacherId = t.Id
Left JOIN CourseMember cm ON cd.Id = cm.CourseDetailsId
Left JOIN Students s ON cm.StudentId = s.Id
Left JOIN Rooms r ON cd.RoomId = r.Id
)
Select Course, Teacher
, ' + @colsRoomsalias + '
, ' + @colsstudentalias + '
FROM (
Select Course, Teacher, RoomName, RoomId,Student, StudentId
From PivotData) src
PIVOT( Max(RoomName) FOR RoomId IN (' + @colsRooms + ')) as P1
PIVOT( Count(Student) FOR StudentId IN (' + @colsStudents + ') ) as P2'
exec (@sql)
您可以使用动态sql查询为两个数据透视列创建别名字符串, 例如,对于学生专栏:
DECLARE @colsStudents AS NVARCHAR(MAX),
@colsstudentalias AS NVARCHAR(MAX),
@colsRooms AS NVARCHAR(MAX),
@colsRoomsalias AS NVARCHAR(MAX)
SELECT @colsStudents = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(100 + Id)
FROM dbo.Students
FOR XML PATH('')
), 1, 1, ''
)
SELECT @colsstudentalias = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(100 + Id)
+ ' as ' + QUOTENAME(ltrim(rtrim(StudentName)))
FROM dbo.Students
FOR XML PATH('')
), 1, 1, ''
)
SELECT @colsRooms = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(Id)
FROM dbo.Rooms
FOR XML PATH('')
), 1, 1, ''
)
SELECT @colsRoomsalias = STUFF
(
(
SELECT DISTINCT ',' + QUOTENAME(Id)
+ ' as ' + QUOTENAME(ltrim(rtrim(RoomName)))
FROM dbo.Rooms
FOR XML PATH('')
), 1, 1, ''
)
--SELECT @colsStudents, @colsstudentalias, @colsRooms, @colsRoomsalias
DECLARE @sql varchar(max)
set @sql = ';With PivotData as (
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,r.Id as RoomId, r.RoomName as RoomName
,100 + s.Id as StudentId, s.StudentName as Student
FROM CourseDetails cd
Left JOIN Courses c ON cd.CourseId = c.Id
Left JOIN Teachers t ON cd.TeacherId = t.Id
Left JOIN CourseMember cm ON cd.Id = cm.CourseDetailsId
Left JOIN Students s ON cm.StudentId = s.Id
Left JOIN Rooms r ON cd.RoomId = r.Id
)
Select Course, Teacher
, ' + @colsRoomsalias + '
, ' + @colsstudentalias + '
FROM (
Select Course, Teacher, RoomName, RoomId,Student, StudentId
From PivotData) src
PIVOT( Max(RoomName) FOR RoomId IN (' + @colsRooms + ')) as P1
PIVOT( Count(Student) FOR StudentId IN (' + @colsStudents + ') ) as P2'
exec (@sql)
我个人会做一些不同的事情。因为您正试图旋转两个单独的列,它们会尖叫着使用
UNPIVOT
函数
unpivot会将多个列转换为行,然后进行透视
由于您有SQL Server 2008,因此可以使用交叉应用
和值:
select id, course, teacher, col, flag
from
(
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,cast(r.Id as varchar(10))as RoomId
, r.RoomName as RoomName
,cast(100 + s.Id as varchar(10)) as StudentId
, s.StudentName as Student
, '1' flag
FROM CourseDetails cd
Left JOIN Courses c
ON cd.CourseId = c.Id
Left JOIN Teachers t
ON cd.TeacherId = t.Id
Left JOIN CourseMember cm
ON cd.Id = cm.CourseDetailsId
Left JOIN Students s
ON cm.StudentId = s.Id
Left JOIN Rooms r
ON cd.RoomId = r.Id
) d
cross apply
(
values ('roomname', roomname),('student',student)
) c (value, col)
看。unpivot生成的结果与此类似:
| ID | COURSE | TEACHER | COL | FLAG |
-------------------------------------------------------------
| 1 | C# 1 - Basics | Marc G. | E7 | 1 |
| 1 | C# 1 - Basics | Marc G. | Penny | 1 |
| 2 | C# 2 - Intermediate | Sam S. | E7 | 1 |
| 2 | C# 2 - Intermediate | Sam S. | Penny | 1 |
| 2 | C# 2 - Intermediate | Sam S. | E7 | 1 |
| 2 | C# 2 - Intermediate | Sam S. | Koothrappali | 1 |
| 3 | C# 3 - Advanced | John S. | A3 | 1 |
| 3 | C# 3 - Advanced | John S. | Cooper | 1 |
您将看到,col
数据包含要透视的所有值。一旦数据进入行中,if将很容易应用一个轴:
select id, course, teacher,
coalesce(A3, '') A3,
coalesce(E7, '') E7,
coalesce(Koothrappali, '') Koothrappali,
coalesce(Cooper, '') Cooper,
coalesce(Penny, '') Penny,
coalesce(Amy, '') Amy
from
(
select id, course, teacher, col, flag
from
(
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,cast(r.Id as varchar(10))as RoomId
, r.RoomName as RoomName
,cast(100 + s.Id as varchar(10)) as StudentId
, s.StudentName as Student
, '1' flag
FROM CourseDetails cd
Left JOIN Courses c
ON cd.CourseId = c.Id
Left JOIN Teachers t
ON cd.TeacherId = t.Id
Left JOIN CourseMember cm
ON cd.Id = cm.CourseDetailsId
Left JOIN Students s
ON cm.StudentId = s.Id
Left JOIN Rooms r
ON cd.RoomId = r.Id
) d
cross apply
(
values ('roomname', roomname),('student',student)
) c (value, col)
) d
pivot
(
max(flag)
for col in (A3, E7, Koothrappali, Cooper, Penny, Amy)
) piv
看
然后,若要将其转换为动态SQL,您只需旋转一列,因此将使用以下命令获取列列表:
select @cols = STUFF((SELECT ',' + QUOTENAME(col)
from
(
select id, roomname col, 1 SortOrder
from rooms
union all
select id, StudentName, 2
from Students
) d
group by id, col, sortorder
order by sortorder, id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
这将得到在透视图中使用的不同房间和学生的列表。因此,最终代码将是:
DECLARE @cols AS NVARCHAR(MAX),
@colsNull AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT ',' + QUOTENAME(col)
from
(
select id, roomname col, 1 SortOrder
from rooms
union all
select id, StudentName, 2
from Students
) d
group by id, col, sortorder
order by sortorder, id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select @colsNull = STUFF((SELECT ', coalesce(' + QUOTENAME(col)+', '''') as '+QUOTENAME(col)
from
(
select id, roomname col, 1 SortOrder
from rooms
union all
select id, StudentName, 2
from Students
) d
group by id, col, sortorder
order by sortorder, id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query
= 'SELECT
id, course, teacher,' + @colsNull + '
from
(
select id, course, teacher, col, flag
from
(
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,cast(r.Id as varchar(10))as RoomId
, r.RoomName as RoomName
,cast(100 + s.Id as varchar(10)) as StudentId
, s.StudentName as Student
, ''1'' flag
FROM CourseDetails cd
Left JOIN Courses c
ON cd.CourseId = c.Id
Left JOIN Teachers t
ON cd.TeacherId = t.Id
Left JOIN CourseMember cm
ON cd.Id = cm.CourseDetailsId
Left JOIN Students s
ON cm.StudentId = s.Id
Left JOIN Rooms r
ON cd.RoomId = r.Id
) d
cross apply
(
values (''roomname'', roomname),(''student'',student)
) c (value, col)
) d
pivot
(
max(flag)
for col in (' + @cols + ')
) p '
execute(@query)
看
注意:我实现了一个用于透视的标志,如果房间或学生有值,这基本上会生成一个Y/N
这给出了一个最终结果:
| ID | COURSE | TEACHER | A3 | E7 | KOOTHRAPPALI | COOPER | PENNY | AMY |
---------------------------------------------------------------------------------------
| 1 | C# 1 - Basics | Marc G. | | 1 | | | 1 | |
| 2 | C# 2 - Intermediate | Sam S. | | 1 | 1 | | 1 | |
| 3 | C# 3 - Advanced | John S. | 1 | | | 1 | | |
| 4 | C# 3 - Advanced | Reed C. | | 1 | 1 | | | |
| 5 | SQL 1 - Basics | Marc G. | 1 | | | | | |
| 6 | SQL 2 - Intermediate | Marc G. | 1 | | | | | |
| 7 | SQL 3 - Advanced | Marc G. | | 1 | | 1 | | 1 |
| 8 | SQL 3 - Advanced | gbn | 1 | | 1 | | | |
作为旁注,还可以使用sql server中的
unpivot
函数取消导入此数据。(参见)我个人会做一些不同的事情。因为您正试图旋转两个单独的列,它们会尖叫着使用UNPIVOT
函数
unpivot会将多个列转换为行,然后进行透视
由于您有SQL Server 2008,因此可以使用交叉应用
和值:
select id, course, teacher, col, flag
from
(
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,cast(r.Id as varchar(10))as RoomId
, r.RoomName as RoomName
,cast(100 + s.Id as varchar(10)) as StudentId
, s.StudentName as Student
, '1' flag
FROM CourseDetails cd
Left JOIN Courses c
ON cd.CourseId = c.Id
Left JOIN Teachers t
ON cd.TeacherId = t.Id
Left JOIN CourseMember cm
ON cd.Id = cm.CourseDetailsId
Left JOIN Students s
ON cm.StudentId = s.Id
Left JOIN Rooms r
ON cd.RoomId = r.Id
) d
cross apply
(
values ('roomname', roomname),('student',student)
) c (value, col)
看。unpivot生成的结果与此类似:
| ID | COURSE | TEACHER | COL | FLAG |
-------------------------------------------------------------
| 1 | C# 1 - Basics | Marc G. | E7 | 1 |
| 1 | C# 1 - Basics | Marc G. | Penny | 1 |
| 2 | C# 2 - Intermediate | Sam S. | E7 | 1 |
| 2 | C# 2 - Intermediate | Sam S. | Penny | 1 |
| 2 | C# 2 - Intermediate | Sam S. | E7 | 1 |
| 2 | C# 2 - Intermediate | Sam S. | Koothrappali | 1 |
| 3 | C# 3 - Advanced | John S. | A3 | 1 |
| 3 | C# 3 - Advanced | John S. | Cooper | 1 |
您将看到,col
数据包含要透视的所有值。一旦数据进入行中,if将很容易应用一个轴:
select id, course, teacher,
coalesce(A3, '') A3,
coalesce(E7, '') E7,
coalesce(Koothrappali, '') Koothrappali,
coalesce(Cooper, '') Cooper,
coalesce(Penny, '') Penny,
coalesce(Amy, '') Amy
from
(
select id, course, teacher, col, flag
from
(
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,cast(r.Id as varchar(10))as RoomId
, r.RoomName as RoomName
,cast(100 + s.Id as varchar(10)) as StudentId
, s.StudentName as Student
, '1' flag
FROM CourseDetails cd
Left JOIN Courses c
ON cd.CourseId = c.Id
Left JOIN Teachers t
ON cd.TeacherId = t.Id
Left JOIN CourseMember cm
ON cd.Id = cm.CourseDetailsId
Left JOIN Students s
ON cm.StudentId = s.Id
Left JOIN Rooms r
ON cd.RoomId = r.Id
) d
cross apply
(
values ('roomname', roomname),('student',student)
) c (value, col)
) d
pivot
(
max(flag)
for col in (A3, E7, Koothrappali, Cooper, Penny, Amy)
) piv
看
然后,若要将其转换为动态SQL,您只需旋转一列,因此将使用以下命令获取列列表:
select @cols = STUFF((SELECT ',' + QUOTENAME(col)
from
(
select id, roomname col, 1 SortOrder
from rooms
union all
select id, StudentName, 2
from Students
) d
group by id, col, sortorder
order by sortorder, id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
这将得到在透视图中使用的不同房间和学生的列表。因此,最终代码将是:
DECLARE @cols AS NVARCHAR(MAX),
@colsNull AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT ',' + QUOTENAME(col)
from
(
select id, roomname col, 1 SortOrder
from rooms
union all
select id, StudentName, 2
from Students
) d
group by id, col, sortorder
order by sortorder, id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select @colsNull = STUFF((SELECT ', coalesce(' + QUOTENAME(col)+', '''') as '+QUOTENAME(col)
from
(
select id, roomname col, 1 SortOrder
from rooms
union all
select id, StudentName, 2
from Students
) d
group by id, col, sortorder
order by sortorder, id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query
= 'SELECT
id, course, teacher,' + @colsNull + '
from
(
select id, course, teacher, col, flag
from
(
Select cd.Id, c.CourseName as Course, t.TeacherName as Teacher
,cast(r.Id as varchar(10))as RoomId
, r.RoomName as RoomName
,cast(100 + s.Id as varchar(10)) as StudentId
, s.StudentName as Student
, ''1'' flag
FROM CourseDetails cd
Left JOIN Courses c
ON cd.CourseId = c.Id
Left JOIN Teachers t
ON cd.TeacherId = t.Id
Left JOIN CourseMember cm
ON cd.Id = cm.CourseDetailsId
Left JOIN Students s
ON cm.StudentId = s.Id
Left JOIN Rooms r
ON cd.RoomId = r.Id
) d
cross apply
(
values (''roomname'', roomname),(''student'',student)
) c (value, col)
) d
pivot
(
max(flag)
for col in (' + @cols + ')
) p '
execute(@query)
看
注意:我实现了一个用于透视的标志,如果房间或学生有值,这基本上会生成一个Y/N
这给出了一个最终结果:
| ID | COURSE | TEACHER | A3 | E7 | KOOTHRAPPALI | COOPER | PENNY | AMY |
---------------------------------------------------------------------------------------
| 1 | C# 1 - Basics | Marc G. | | 1 | | | 1 | |
| 2 | C# 2 - Intermediate | Sam S. | | 1 | 1 | | 1 | |
| 3 | C# 3 - Advanced | John S. | 1 | | | 1 | | |
| 4 | C# 3 - Advanced | Reed C. | | 1 | 1 | | | |
| 5 | SQL 1 - Basics | Marc G. | 1 | | | | | |
| 6 | SQL 2 - Intermediate | Marc G. | 1 | | | | | |
| 7 | SQL 3 - Advanced | Marc G. | | 1 | | 1 | | 1 |
| 8 | SQL 3 - Advanced | gbn | 1 | | 1 | | | |
作为旁注,还可以使用sql server中的
unpivot
函数取消导入此数据。(请参阅)我将更深入地研究上面的两个答案,并将它们与下面的答案进行比较
- 我的问题是用
函数填充局部变量@RoomNames和@StudentNames。一个原因是我选择了数据类型Stuff()
,而不是nchar(120)
列的nvarchar(120)
,StudentName
RoomName
- 我遇到的另一个问题是,新的列名(Student而不是StudentName)没有被识别;因此,我在这条语句中将它们替换为
:*
Select*From('+@PivotSrc+N')src
SELECT@RoomIds=isnull(@RoomIds+,“,”)+'['+Cast(Id为nvarchar(20))+']',而不是STUFF()
,因为我发现它更短,更容易阅读,所以我现在使用它
工作溶液
我将更深入地研究上面的两个答案,并将它们与下面的答案进行比较
- 我的问题是用
Stuff()
函数填充局部变量@RoomNames和@StudentNames。一个原因是我选择了数据类型nchar(120)
,而不是
nvarchar(120)
列的StudentName
,RoomName
- 我遇到的另一个问题是,新的列名(Student而不是StudentName)没有被识别;因此,我在这条语句中将它们替换为
*
:Select*From('+@PivotSrc+N')src
建议使用SELECT@RoomIds=isnull(@RoomIds+,”,“)+'['+Cast(Id为nvarchar(20))+']',而不是STUFF()