将多个列拆分为多个行

时间:2010-09-09 16:21:16

标签: sql sql-server unpivot

我有一个这种结构的表。

UserID  | UserName  | AnswerToQuestion1 | AnswerToQuestion2 | AnswerToQuestion3
1       | John      | 1                 | 0                 | 1
2       | Mary      | 1                 | 1                 | 0

我无法弄清楚我会用什么SQL查询来获得这样的结果集:

UserID  | UserName  | QuestionName      | Response
1       | John      | AnswerToQuestion1 | 1
1       | John      | AnswerToQuestion2 | 0
1       | John      | AnswerToQuestion3 | 1
2       | Mary      | AnswerToQuestion1 | 1
2       | Mary      | AnswerToQuestion2 | 1
2       | Mary      | AnswerToQuestion3 | 0

我正在尝试将三列拆分为三个单独的行。这可能吗?

3 个答案:

答案 0 :(得分:7)

SELECT
   Y.UserID,
   Y.UserName,
   QuestionName = 'AnswerToQuestion' + X.Which,
   Response =
      CASE X.Which
      WHEN '1' THEN AnswerToQuestion1
      WHEN '2' THEN AnswerToQuestion2
      WHEN '3' THEN AnswerToQuestion3
      END
FROM
   YourTable Y
   CROSS JOIN (SELECT '1' UNION ALL SELECT '2' UNION ALL SELECT '3') X (Which)

这对UNPIVOT(有时更好)也同样有效,并且也适用于SQL 2000。

我利用问题的相似性来创建QuestionName列,但当然这将适用于不同的问题名称。

请注意,如果您的问题列表很长或问题名称很长,您可以在X表中试验2列,一个用于问题编号,一个用于问题名称。或者,如果您已经有一个包含问题列表的表格,那么CROSS就会加入。如果某些问题是NULL,那么最简单的方法是将上述查询放在CTE或派生表中,然后添加WHERE Response IS NOT NULL

答案 1 :(得分:6)

根据Inside Microsoft SQL Server 2008: T-SQL Querying中的Itzik Ben-Gan,SQL Server在解开表格时会经历三个步骤:

  
      
  1. 生成副本
  2.   
  3. 提取元素
  4.   
  5. 删除包含NULL的行
  6.   

第1步:生成副本

创建一个虚拟表,其中包含orignal表中每个未被分配的列的每一行的副本。 此外,列名的字符串存储在新列中(将其称为QuestionName列)。 *注意:我将其中一列中的值修改为NULL以显示完整的过程。

UserID  UserName  AnswerTo1 AnswerToQ2 AnswerToQ3 QuestionName
1       John      1         0          1          AnswerToQuestion1
1       John      1         0          1          AnswerToQuestion2
1       John      1         0          1          AnswerToQuestion3
2       Mary      1         NULL       1          AnswerToQuestion1
2       Mary      1         NULL       1          AnswerToQuestion2
2       Mary      1         NULL       1          AnswerToQuestion3

第2步:提取元素

然后创建另一个表,为源对应的每个值创建一个新行 到QuestionName列中的字符串值。该值存储在一个新列中(将其称为响应列)。

UserID  UserName  QuestionName        Response
1       John      AnswerToQuestion1   1
1       John      AnswerToQuestion2   0
1       John      AnswerToQuestion3   1
2       Mary      AnswerToQuestion1   1
2       Mary      AnswerToQuestion2   NULL
2       Mary      AnswerToQuestion3   1

第3步:删除包含NULLS的行

此步骤筛选出在“响应”列中使用空值创建的所有行。换一种说法, 如果任何AnswerToQuestion列具有空值,则不会将其表示为不显示的行。

UserID  UserName  QuestionName        Response
1       John      AnswerToQuestion1   1
1       John      AnswerToQuestion2   0
1       John      AnswerToQuestion3   1
2       Mary      AnswerToQuestion1   1
2       Mary      AnswerToQuestion3   1

如果您按照这些步骤操作,则可以

  1. CROSS根据每个AnswerToQuestion加入表中的所有行 列名称以获取行副本
  2. 基于“填充响应”列 在匹配源列和QuestionName
  3. 删除NULL以获得相同的内容 结果没有使用UNPIVOT。
  4. 以下示例:

    DECLARE @t1 TABLE (UserID INT, UserName VARCHAR(10), AnswerToQuestion1 INT, 
      AnswertoQuestion2 INT, AnswerToQuestion3 INT
    ) 
    
    INSERT @t1 SELECT 1, 'John', 1, 0, 1 UNION ALL SELECT 2, 'Mary', 1, NULL, 1 
    
    SELECT
      UserID,
      UserName,
      QuestionName,
      Response
    FROM (
      SELECT
        UserID,
        UserName,
        QuestionName,
        CASE QuestionName
          WHEN 'AnswerToQuestion1' THEN AnswerToQuestion1
          WHEN 'AnswerToQuestion2' THEN AnswertoQuestion2
          ELSE AnswerToQuestion3 
        END AS Response 
      FROM @t1 t1
          CROSS JOIN (
            SELECT 'AnswerToQuestion1' AS QuestionName
            UNION ALL SELECT 'AnswerToQuestion2'
            UNION ALL SELECT 'AnswerToQuestion3'
          ) t2
        ) t3
    WHERE Response IS NOT NULL
    

答案 2 :(得分:5)

假设SQL Server 2005+,您可以使用UNPIVOT

;with YourTable as
(
SELECT 1 UserID,'John' UserName,1 AnswerToQuestion1,0 AnswerToQuestion2,1 AnswerToQuestion3 
UNION ALL
SELECT 2, 'Mary', 1, 1, 0
)
SELECT UserID, UserName, QuestionName, Response
FROM YourTable
UNPIVOT
   (Response FOR QuestionName IN 
      (AnswerToQuestion1, AnswerToQuestion2,AnswerToQuestion3)
)AS unpvt;