根据Itzik Ben-Gan在《
深入解析Microsoft SQL Server 2008:T-SQL查询》中的说法,当对表进行非标准化操作时,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
步骤三:删除空行
该步骤筛选掉响应列中包含 null 值的任何行。换句话说,如果任何一个问题回答列具有 null 值,则不会表示为未展开的行。
UserID UserName QuestionName Response
1 John AnswerToQuestion1 1
1 John AnswerToQuestion2 0
1 John AnswerToQuestion3 1
2 Mary AnswerToQuestion1 1
2 Mary AnswerToQuestion3 1
如果您按照以下步骤操作,您可以:
- 使用CROSS JOIN将表中的所有行与每个AnswerToQuestion列名进行连接,以获取行副本
- 根据匹配源列和QuestionName填充Response列
- 删除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
'AnswerToQuestion' + X.Which AS QuestionName
。 - ErikECROSS JOIN (VALUES ('1'), ('2'), ('3')) X (Which)
。 - Jason Roman