根据 Itzik Ben-Gan 在Inside Microsoft SQL Server 2008: T-SQL Querying 中的说法,SQL Server 在取消透视表时会经历三个步骤:
- 生成副本
- 提取元素
- 删除带有 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
如果你按照这些步骤,你可以
- 针对每个 AnswerToQuestion 列名 CROSS JOIN 表中的所有行以获取行副本
- 根据匹配的源列和 QuestionName 填充 Response 列
- 在不使用 UNPIVOT 的情况下删除 NULL 以获得相同的结果。
下面的一个例子:
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