【问题标题】:Split Multiple Columns into Multiple Rows将多列拆分为多行
【发布时间】:2010-09-09 16:21:16
【问题描述】:

我有一个具有这种结构的表。

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

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

【问题讨论】:

    标签: sql sql-server unpivot


    【解决方案1】:
    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 JOIN 到那个表格。如果某些问题为 NULL,那么最简单的方法是将上述查询放入 CTE 或派生表中,然后添加 WHERE Response IS NOT NULL

    【讨论】:

    • 我得到“列问题名称不存在”
    • 不,我的查询工作正常。只有通过尝试在查询中的其他位置引用别名 QuestionName 才能得到该错误。将此查询放入派生表并从中进行选择,然后您可以对其设置条件。您使用的是 SQL Server,对吗?
    • 哦,不,我使用的是常规 SQL。没有看到 sql-server 标签。谢谢,最终使用 8kb 的答案和你的答案弄清楚了。
    • 对于您想要的其他 DBMS 'AnswerToQuestion' + X.Which AS QuestionName
    【解决方案2】:

    假设 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;
    

    【讨论】:

    • 抱歉回复晚了,感谢您的回复。除了使用 UNPIVOT 运算符,还有其他选择吗?
    【解决方案3】:

    根据 Inside Microsoft SQL Server 2008: T-SQL Querying 中的 Itzik Ben-Gan 所述,SQL Server 在取消透视表时会执行三个步骤:

    1. 生成副本
    2. 提取元素
    3. 删除带有 NULL 的行

    第 1 步:生成副本

    创建了一个虚拟表,其中包含原始表中每一行的副本,其中每一列被取消透视。 此外,列名的字符串存储在新列中(称为 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 的行

    此步骤会过滤掉在 Response 列中使用空值创建的所有行。换句话说, 如果任何 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. 针对每个 AnswerToQuestion 交叉连接表中的所有行 列名以获取行副本
    2. 填充基于响应列 在匹配源列和 QuestionName
    3. 删除 NULL 以获得相同的值 不使用 UNPIVOT 的结果。

    下面的例子:

    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
    

    【讨论】:

    • 非常感谢您的优质回复。我真的很喜欢你的回复,因为它是最详细的,但我可能不得不在你之前几个小时回答 Emtucifor。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多