一般来说有两种方法(嗯,应该是三种):
- 使用大量输入来创建涵盖所有列的语句。这很丑陋但很快。最大的缺点:将来添加一列将迫使您重新进行查询。
- 使用通用方法,该方法适用于任意数量的列。最大的缺点:这不会很快。
- 动态 SQL:使用元数据创建建议 1 的语句。动态。最大的缺点:这在即席/内联查询中永远不会起作用。
为了向您展示一种通用方法,您可以对此进行测试:
DECLARE @tbl TABLE(col1 VARCHAR(10),col2 VARCHAR(10),col3 VARCHAR(10),col4 INT,col5 INT,col6 INT);
INSERT INTO @tbl VALUES
('a','b','c',NULL,500,200)
,('w','x','y',1000,300,NULL)
,('z','g','h',200,NULL,600);
--通用的反透视查询
SELECT t.col1,col2,col3
,ROW_NUMBER() OVER(PARTITION BY col1,col2,col3 ORDER BY B.attr) AS GroupIndex
,B.attr.value('local-name(.)','nvarchar(max)') ColumnName
,B.attr.value('.','int') ColumnValue
FROM @tbl t
CROSS APPLY(SELECT(SELECT t.* FOR XML RAW,TYPE)
.query('<cols>{/row/@*[not(local-name()=("col1","col2","col3"))]}</cols>')) A(x)
CROSS APPLY A.x.nodes('/cols/@*') B(attr);
--我们可以在 PIVOT 查询中使用它
SELECT p.*
FROM
(
SELECT t.col1,col2,col3
,ROW_NUMBER() OVER(PARTITION BY col1,col2,col3 ORDER BY B.attr) AS GroupIndex
,B.attr.value('local-name(.)','nvarchar(max)') ColumnName
,B.attr.value('.','int') ColumnValue
FROM @tbl t
CROSS APPLY(SELECT(SELECT t.* FOR XML RAW,TYPE)
.query('<cols>{/row/@*[not(local-name()=("col1","col2","col3"))]}</cols>')) A(x)
CROSS APPLY A.x.nodes('/cols/@*') B(attr)
) intermediateResult
PIVOT
(
MAX(ColumnValue) FOR ColumnName IN(col4,col5,col6)
)p;
简而言之:
- 我们使用
APPLY 为您的列在 col1、col2 和col3 创建中间XML 表示。为了实现这一点,我们首先创建一个 XML 并使用query() 将除 col1、col2、col3 之外的所有列作为每行的属性返回。
- 我们使用 XML 的默认值省略 NULL 值
- 我们将此作为列
A.x 添加到结果集中。
- 另一个
APPLY 将调用XML 方法.nodes()。这将为每个现有属性添加一行。
-
intermediateResult 是一个未透视 集。 这是您实际上应该用于存储的格式...
- 现在我们可以使用
PIVOT 来获得所需的输出。
最大的优点是,您可以添加col7 而无需更改它(在通用部分)。 PIVOT 的输出列表将明确需要任何新列。