1

イントロと問題

私の例では、教師、学生、コースがあります。どのコースが誰によってどの部屋で教えられているか、このコースのすべての学生について概要を知りたいです。私は基本的なセットアップを実行しています (いくつかのハンドコーディングされたステートメントを使用)。しかし、今まで私は正しい STUFF ステートメントを準備する運がありませんでした:

  • @colsStudents列ヘッダーに名前を入れて、rooms.id と students.id の間の競合を避けるために ID をいじる必要がないように準備します (100 を追加) 。
  • @colsRoomsルーム名をハードロックする必要がないように準備します
  • を使用してすべてをまとめる EXEC sp_executesql @sql;

最後に、このスキーマとデータを作成するためのすべての sql-statements を見つけることができます。

テーブル図

募集結果概要コース、

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 に対して返されます。students.id 列と rooms.id 列の間の競合を避けるために、各 ID に 100 を追加しました。

イントロで述べたように、このようなものを動的に作成する必要があります

[1] as RoomName_1, [2] as RoomName_1 -- RoomColumns
[1] as StudentName1, [2] as StudentName2, ... ,[4] as Amy -- StudentColumns

しかし、スタッフステートメントでの私の試みはすべて失敗しました。

テーブルとデータを作成するためのすべての 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

私は個人的にこれを少し違う方法で行います。関数を使用するように叫ぶ2つの別々の列をピボットしようとしているためですUNPIVOT

アンピボットは、複数の列を行に変換してからピボットします。

CROSS APPLYSQL 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)

デモを参照してください。アンピボットにより、次のような結果が生成されます。

| 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ピボットするすべての値がデータに含まれていることがわかります。データが行に入ったら、1 つのピボットを簡単に適用できます。

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 に変換するには、1 つの列のみをピボットするため、次を使用して列のリストを取得します。

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 |        |       |     |

補足として、このデータはunpivot、SQL サーバーの関数を使用してピボットを解除することもできます。( 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

上記の両方の回答を詳しく見て、以下の回答と比較します。

  • 私の問題は、ローカル変数 @RoomNames と @StudentNames にStuff()関数を入力することでした。理由の 1 つは、列の代わりnchar(120)に データ型を選択したことです。nvarchar(120)StudentNameRoomName
  • 私が抱えていたもう1つの問題は、新しいcolumnNames(StudentNameではなくStudent)が認識されないことでした。*したがって、このステートメントではそれらを次のように置き換えました。Select * From (' + @PivotSrc + N') src

Philip KelleySELECT @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 に答える