T-SQL 透视表?是否可以根据行值创建表列?

21

是否有可能旋转T-SQL(2005)以使得第一列的行值成为输出表格的列标题?

我知道这不是PIVOT的真正用途,但这正是我需要的——请求一个表格,其中列在事先未知,因为它们已作为值输入到表格中。

实际上,甚至一个hack也会很好。

2个回答

27

我强烈推荐Itzik Ben-Gan的《深入浅出Microsoft SQL Server 2008:T-SQL编程》一书,其中有关于如何构建动态PIVOT的示例。

-- Creating and Populating the Orders Table
USE tempdb;
GO

IF OBJECT_ID('dbo.Orders') IS NOT NULL
DROP TABLE dbo.Orders;
GO

CREATE TABLE dbo.Orders
(
orderid   int        NOT NULL PRIMARY KEY NONCLUSTERED,
orderdate datetime   NOT NULL,
empid     int        NOT NULL,
custid    varchar(5) NOT NULL,
qty       int        NOT NULL
);

CREATE UNIQUE CLUSTERED INDEX idx_orderdate_orderid
ON dbo.Orders(orderdate, orderid);

INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(30001, '20020802', 3, 'A', 10);
INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(10001, '20021224', 1, 'A', 12);
INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(10005, '20021224', 1, 'B', 20);
INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(40001, '20030109', 4, 'A', 40);
INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(10006, '20030118', 1, 'C', 14);
INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(20001, '20030212', 2, 'B', 12);
INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(40005, '20040212', 4, 'A', 10);
INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(20002, '20040216', 2, 'C', 20);
INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(30003, '20040418', 3, 'B', 15);
INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(30004, '20020418', 3, 'C', 22);
INSERT INTO dbo.Orders(orderid, orderdate, empid, custid, qty)
VALUES(30007, '20020907', 3, 'D', 30);
GO

-- Static PIVOT
SELECT *
FROM (SELECT custid, YEAR(orderdate) AS orderyear, qty
FROM dbo.Orders) AS D
PIVOT(SUM(qty) FOR orderyear IN([2002],[2003],[2004])) AS P;
GO

-- Dynamic PIVOT
DECLARE @T AS TABLE(y INT NOT NULL PRIMARY KEY);

DECLARE
@cols AS NVARCHAR(MAX),
@y    AS INT,
@sql  AS NVARCHAR(MAX);

-- Construct the column list for the IN clause
-- e.g., [2002],[2003],[2004]
SET @cols = STUFF(
(SELECT N',' + QUOTENAME(y) AS [text()]
FROM (SELECT DISTINCT YEAR(orderdate) AS y FROM dbo.Orders) AS Y
ORDER BY y
FOR XML PATH('')),
1, 1, N'');

-- Construct the full T-SQL statement
-- and execute dynamically
SET @sql = N'SELECT *
FROM (SELECT custid, YEAR(orderdate) AS orderyear, qty
FROM dbo.Orders) AS D
PIVOT(SUM(qty) FOR orderyear IN(' + @cols + N')) AS P;';

EXEC sp_executesql @sql;
GO

倒数第二行只需要执行 (@sql)。 - Johnny Bones
@JohnnyBones 你为什么会这样想呢?EXEC sp_executesql @sql 是正确的语法。如果你尝试使用 EXEC (@sql),它将会尝试查找该文本对应的存储过程而导致失败。 - user275683

5
稍微更好的旋转查询如下所示:
-- Static PIVOT
WITH PivotData AS
(
SELECT custid, YEAR(orderdate) AS orderyear, qty
FROM dbo.Orders
)
SELECT custid, [2002], [2003], [2004]
FROM PivotData
PIVOT(SUM(qty) FOR orderyear IN([2002],[2003],[2004])) AS P;

我更喜欢使用通用表达式(CTE)的方式,而不是派生表,因为我认为它更容易理解。Itzik也是这样认为的,在他的书《查询Microsoft SQL Server 2012》中推荐了这种方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接