SQL Server将多个列转换为多行并包含空值

时间:2018-01-17 08:01:11

标签: sql-server

我有下表:

+----------------------------------------------------------------------+
| Student   Class1    Class1_score  Class2   Class2_score   Class3.... |
+----------------------------------------------------------------------+
| Alex      English   70            Maths    100            NULL       |
| John      Science   50            NULL     NULL           NULL       |
| Bruce     Maths     50            Science  50             English    |
+----------------------------------------------------------------------+

我希望转向以下内容:

+--------------------------+
| Student   Class    Score |
+--------------------------+
| Alex      English   70   |
| Alex      Maths     100  |
| Alex      Science   NULL |
| Alex      History   NULL |
| John      English   NULL |
+--------------------------+

其中包含不属于该特定学生原始表格的类的NULL(例如Science for Alex)

如何在SQL中实现这一目标?

4 个答案:

答案 0 :(得分:1)

假设类类型只在classX列中出现一次:

select student,
       'English' class,
       coaslesce(case when class1 = 'english' then Class1_score end, 
                 case when class2 = 'english' then Class2_score end,
                 case when class3 = 'english' then Class3_score end,
                 case when class4 = 'english' then Class4_score end) score
from your_table

union

select student,
       'Maths' class,
       coaslesce(case when class1 = 'Maths' then Class1_score end, 
                 case when class2 = 'Maths' then Class2_score end,
                 case when class3 = 'Maths' then Class3_score end,
                 case when class4 = 'Maths' then Class4_score end) score
from your_table

union

select student,
       'Science' class,
       coaslesce(case when class1 = 'Science' then Class1_score end, 
                 case when class2 = 'Science' then Class2_score end,
                 case when class3 = 'Science' then Class3_score end,
                 case when class4 = 'Science' then Class4_score end) score
from your_table

union

select student,
       'History' class,
       coaslesce(case when class1 = 'History' then Class1_score end, 
                 case when class2 = 'History' then Class2_score end,
                 case when class3 = 'History' then Class3_score end,
                 case when class4 = 'History' then Class4_score end) score
from your_table

答案 1 :(得分:1)

如果您希望每个Class列值都包含所有Student列值,则声明2个表变量,一个用于存储Student列值的唯一值,另一个用于存储Class列值的唯一值。然后在这两个表之间进行完全外连接,并将此结果集用作子集,并将其与另一个表变量连接,该变量包含每个student classscore的单独行。所有这些都可以通过执行动态SQL查询来完成。

<强>查询

-- table variabe to store unique class values

declare @sql as varchar(max) = 'declare @tbl_class as table([class] varchar(100));'
                             + 'insert into @tbl_class([class]) ';
select @sql += stuff((
        select ' union select [' + [column_name] + '] from [' + [table_name] + '] '
        + 'where [' + [column_name] + '] is not null'
        from information_schema.columns
        where [table_name] = 'your_table_name'
        and [column_name] like 'class%[0-9]'
        order by [ordinal_position]
        for xml path('')
    )
    , 1, 7, ''
) + ';';

-- table variabe to store unique student values

select @sql += 'declare @tbl_student as table([student] varchar(100));'
            + 'insert into @tbl_student([student]) '
            + 'select distinct [Student] from [your_table_name];';

-- table variable to store student score and class values one by one

select @sql += 'declare @tbl_scores as table'
            + '([student] varchar(100), [class] varchar(100), [score] int);'
            + 'insert into @tbl_scores([student], [class], [score]) ';

select @sql += stuff((
        select ' union all select [Student], ' 
        + '[' + t.[col_1] + '] as [class],'  
        + '[' + t.[col_2] + '] as [score] '
        + 'from [' + t.[table_name] + '] '
        from (
            select [column_name] as [col_1], [column_name] + '_score' as [col_2], 
            [ordinal_position], [table_name]
            from information_schema.columns
            where [table_name] = 'your_table_name'
            and [column_name] like 'class%[0-9]'
        ) t
        order by t.[ordinal_position]
        for xml path('')
    )
    , 1, 10, ''
) + ';';

-- final result

select @sql += 'select t.[student], t.[Class], tsc.[Score] from('
            + 'select ts.[student], tc.[Class]'
            + ' from @tbl_student as ts '
            + ' full outer join @tbl_class as tc '
            + 'on 1 = 1 ) t '
            + 'left join @tbl_scores as tsc '
            + 'on t.[student] = tsc.[student] '
            + 'and t.[Class] = tsc.[Class];';

exec(@sql);

<强> Find a demo here

答案 2 :(得分:0)

你实际上是在寻找unpivot(从宽到高),而不是透视。一种应该适用于几乎所有数据库的方法都使用一系列联合:

SELECT Student, Class, Score
FROM
(
    SELECT Student, Class1 AS Class, Class1_score AS Score, 1 AS position
    FROM yourTable
    UNION ALL
    SELECT Student, Class2, Class2_score, 2
    FROM yourTable
    UNION ALL
    SELECT Student, Class3, Class3_score, 3
    FROM yourTable
) t
ORDER BY
    Student, position;

答案 3 :(得分:0)

示例数据

 IF OBJECT_ID('dbo.tempdata') IS NOT NULL

DROP TABLE tempdata
;With cte(Student ,  Class1 ,   Class1_score,  Class2,   Class2_score,   Class3,Class3_score)
AS
(
SELECT 'Alex' ,'English', 70,'Maths'   , 100 ,  NULL    ,NULL   UNION ALL
SELECT 'John' ,'Science', 50,NULL      ,NULL ,  NULL    ,NULL   UNION ALL
SELECT 'Bruce','Maths'  , 50,'Science' , 50  , 'English',NULL
)
SELECT * INTO tempdata FROM cte

SELECT * FROM tempdata

使用交叉应用和动态SQL

DECLARE @DynamicColumns NVARCHAR(max)
        ,@Sql NVARCHAR(max)

;WITH cte
AS
(
SELECT  COLUMN_NAME, 
        DENSE_RANK()OVER(ORDER BY SUBSTRING(COLUMN_NAME,0,7)) As BatchNo
 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='tempdata' AND COLUMN_NAME <>'Student'
 )
SELECT @DynamicColumns=STUFF((SELECT ', '+ReqCol FROM
     (
        SELECT   
             DISTINCT '('+STUFF((SELECT  ', '+COLUMN_NAME 
            FROM CTE i WHERE i.BatchNo=o.BatchNo
            FOR XML PATH ('')),1,1,'') + ')' AS ReqCol
        FROM cte o
 )dt
 FOR XML PATH ('')),1,1,'')


SET @Sql='SELECT  Student
                 ,Class
                 ,Score 
        FROM tempdata
             CROSS APPLY (VALUES '+@DynamicColumns+'
                        ) As Dt (Class, Score)
            WHERE Class IS NOT NULL

'

PRINT @Sql
EXEC (@Sql)

结果

Student Class   Score
---------------------
Alex    English 70
Alex    Maths   100
John    Science 50
Bruce   Maths   50
Bruce   Science 50
Bruce   English NULL