1

介绍和问题

在我的示例中,我有教师、学生和课程。我想概述一下哪个课程由谁在哪个房间教授,以及这门课程中的所有学生。我有基本的设置运行(带有一些手工编码的语句)。但直到现在我还没有准备好正确的 STUFF 声明:

  • 做好准备@colsStudents,以便我可以将名称放在列标题中,并且无需弄乱 id(添加 100)以避免rooms.id 和students.id 之间的冲突
  • 准备@colsRooms好让我不必硬编码房间名称
  • 通过使用将我放在一起 EXEC sp_executesql @sql;

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

表格图

想要的结果概述课程,

我想旋转列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    |   

到目前为止我有什么

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

透视结果

什么不见​​了

上述声明是手工准备的。由于我事先不知道房间或学生,我需要为列房间和学生动态创建枢轴语句。在 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                    

这将返回[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

但是我对 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)
4

3 回答 3

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)

请参阅演示。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数据包含您想要透视的所有值。一旦数据在行中,如果将很容易应用一个枢轴:

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 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,'')

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

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)

请参阅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 |        |       |     |

附带说明一下,也可以使用unpivotsql server 中的函数对这些数据进行反透视。(请参阅带有 unpivot 的演示

于 2013-04-01T22:10:37.150 回答
1

您可以使用动态 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 演示

于 2013-04-01T22:01:10.510 回答
0

我将更深入地研究上面的两个答案,并将它们与下面的答案进行比较。

  • 我的问题是用Stuff()函数填充局部变量@RoomNames 和@StudentNames。一个原因是我选择了数据类型nchar(120)而不是 nvarchar(120)StudentNameRoomName.
  • 我遇到的另一个问题是新的 columnNames(学生而不是 StudentName)无法识别;*因此我在此声明中将它们替换为:Select * From (' + @PivotSrc + N') src

Philip Kelley建议使用它SELECT @RoomIds = isnull(@RoomIds + ',', '') + '[' + Cast(Id as nvarchar(20))+ ']' FROM Rooms来代替,STUFF() 因为我发现它更短更容易阅读,我现在正在使用它。

工作解决方案

DECLARE @StudentNames NVARCHAR(2000),    
    @RoomIds NVARCHAR(2000),
    @RoomNames NVARCHAR(2000),
    @PivotSrc NVARCHAR(MAX),
    @PivotBase NVARCHAR(MAX);
SELECT @StudentNames = isnull(@StudentNames + ',', '') + '[' + StudentName + ']' FROM Students
SELECT @RoomIds = isnull(@RoomIds + ',', '') + '[' + Cast(Id as nvarchar(20))+ ']' FROM Rooms
SELECT @RoomNames = isnull(@RoomNames + ',', '') + '[' + RoomName + ']' FROM Rooms

SET @PivotSrc = N'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'

SET @PivotBase = N' Select Course, Teacher, ' 
        + @RoomNames + N', ' 
    + @StudentNames + N' FROM (
       Select * From (' + @PivotSrc + N') src
       PIVOT( Max(RoomName) FOR RoomName IN ('+@RoomNames+ N')) as P1
           PIVOT( Count(Student) FOR Student IN ('+@StudentNames+N') ) as P2) as T'

execute(@PivotBase)
于 2013-04-02T00:08:12.847 回答