透视3个表中的2列并创建pivot-column-names以避免冲突 - SQL-Server 2008R2

sur*_*gle 1 sql pivot sql-server-2008

介绍和问题

在我的例子中,我有老师,学生和课程.我想概述哪个课程由谁在本课程的哪些房间和所有学生中进行教学.我有基本的设置runnig(带有一些手动编码的语句).但直到现在我没有运气准备正确的STUFF声明:

  • 准备,@colsStudents以便我可以将名称放在列标题中,并删除混乱ID(添加100)的需要,以避免在rooms.id和students.id之间发生冲突
  • 做好准备,@colsRooms这样我就不必为房间名称加油
  • 通过使用我把所有人放在一起 EXEC sp_executesql @sql;

您可以找到所有sql语句来创建此架构和最后的数据.

表Diagramm

通缉成绩概述课程,

我想转动列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    |   
Run Code Online (Sandbox Code Playgroud)

到目前为止我有什么

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
    , [1] as A3, [2] as E7 -- RoomColumns
    , [101] as Koothrappali, [102] as Cooper, [103] as Penny, [104] as Amy -- StudentColumns
    FROM (
        Select Course, Teacher, RoomName, RoomId,Student, StudentId
        From PivotData) src
    PIVOT( Max(RoomName) FOR RoomId IN ([1],[2])) as P1
    PIVOT( Count(Student) FOR StudentId IN ([101],[102],[103],[104]) ) as P2
Run Code Online (Sandbox Code Playgroud)

透视结果

缺什么

以上陈述是手工准备的.由于我不提前知道房间或学生,我需要动态创建列房间和学生的透视声明.在SO上有很多例子如何做到这一点.通常的方法是使用STUFF:

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                    
Run Code Online (Sandbox Code Playgroud)

这将返回[101],[102],[103],[104]学生ID.我为每个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
Run Code Online (Sandbox Code Playgroud)

但我所有尝试使用stuff语句都失败了.

用于创建表和数据的所有SQL语句

CREATE TABLE [dbo].[Teachers](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [TeacherName] [nvarchar](120) NULL,
    CONSTRAINT PK_Teachers PRIMARY KEY CLUSTERED (Id))

CREATE TABLE [dbo].[Students](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [StudentName] [nvarchar](120) NULL,
    CONSTRAINT PK_Students PRIMARY KEY CLUSTERED (Id))

CREATE TABLE [dbo].[Courses](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [CourseName] [nvarchar](120) NULL,
    CONSTRAINT PK_Courses PRIMARY KEY CLUSTERED (Id))

CREATE TABLE [dbo].[Rooms](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [RoomName] [nchar](120) NULL,
    CONSTRAINT PK_Rooms PRIMARY KEY CLUSTERED (Id))

CREATE TABLE [dbo].[CourseDetails](
  [Id] [int] IDENTITY(1,1) NOT NULL,
  [CourseId] [int] NOT NULL,
  [TeacherId] [int] NOT NULL,
  [RoomId] [int] NOT NULL,  
  CONSTRAINT PK_CourseDetails PRIMARY KEY CLUSTERED (Id),
  CONSTRAINT FK_CourseDetails_Teachers_Id FOREIGN Key (TeacherId)
    REFERENCES dbo.Teachers (Id),   
  CONSTRAINT FK_CourseDetails_Courses_Id FOREIGN Key (CourseId)
    REFERENCES dbo.Courses (Id),    
  CONSTRAINT FK_CourseDetails_Rooms_Id FOREIGN Key (RoomId)
    REFERENCES dbo.Rooms (Id)       
)       


CREATE TABLE [dbo].[CourseMember](
  [Id] [int] IDENTITY(1,1) NOT NULL,
  [CourseDetailsId] [int] NOT NULL,
  [StudentId] [int] NOT NULL,
  CONSTRAINT PK_CourseMember PRIMARY KEY CLUSTERED (Id),
  CONSTRAINT FK_CourseMember_CourseDetails_Id FOREIGN Key (CourseDetailsId)
    REFERENCES dbo.CourseDetails (Id),   
  CONSTRAINT FK_CourseMember_Students_Id FOREIGN Key (StudentId)
    REFERENCES dbo.Students (Id)     
)



INSERT INTO dbo.Courses (CourseName)
VALUES ('SQL 1 - Basics'),
    ('SQL 2 - Intermediate'),
    ('SQL 3 - Advanced'),   
    ('C# 1 - Basics'),
    ('C# 2 - Intermediate'),    
    ('C# 3 - Advanced')     

INSERT INTO dbo.Students (StudentName)
VALUES
   ('Koothrappali'),   
   ('Cooper'),
   ('Penny'),   
   ('Amy') 

INSERT INTO dbo.Teachers (TeacherName)
VALUES
   ('gbn '),
   ('Sam S.'),
   ('Marc G.'),   
   ('Reed C.'),
   ('John S.')

INSERT INTO dbo.Rooms (RoomName)
VALUES ('A3'), ('E7')


INSERT [dbo].[CourseDetails] (CourseId, TeacherId, RoomId) 
    VALUES (4, 3, 2),(5, 2, 2),
        (6, 5, 1),(6, 4, 2),
        (1,3,1),(2,3,1),(3,3,2),
        (3,1,1)

INSERT [dbo].[CourseMember] (CourseDetailsId, StudentId) 
    VALUES (1,3),(2,3),(2,1),(3,2),(4,1),(7,2),(7,4),(8,1)
Run Code Online (Sandbox Code Playgroud)

Tar*_*ryn 5

我个人会这样做有点不同.因为你试图转动两个尖叫的单独列来使用该UNPIVOT函数.

unpivot会将您的多个列转换为行然后进行透视.

由于您有SQL Server 2008,因此可以使用CROSS APPLY和值:

  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)
Run Code Online (Sandbox Code Playgroud)

演示.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 |
Run Code Online (Sandbox Code Playgroud)

您将看到col数据包含您要转动的所有值.一旦数据在行中,if将很容易应用一个pivot:

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
Run Code Online (Sandbox Code Playgroud)

请参阅SQL Fiddle with Demo.

然后,要将其转换为动态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,'')
Run Code Online (Sandbox Code Playgroud)

这将获得在枢轴中使用的不同房间和学生的列表.所以最终的代码是:

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)
Run Code Online (Sandbox Code Playgroud)

请参阅SQL Fiddle with Demo.

注意我实现了一个在枢轴中使用的标志,如果房间或学生有值,这基本上会产生一个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 |        |       |     |
Run Code Online (Sandbox Code Playgroud)

另请注意,此数据也可以使用unpivotsql server中的函数进行转换.(参见使用unpivot演示)