SQL Wizardry 第七部分 - PIVOT 和任意数据列表






4.98/5 (13投票s)
讨论 PIVOT 以及在 SQL Server 中将数据列转换为行的其他方法。
引言
在之前的文章中,我讨论了如何向 SQL Server 传递任意数量的值。但是,如果您想获取任意数量的值,并以清晰的数据集表示出来呢?这就是我们今天要探讨的。
数据
如果您还没有安装 AdventureWorks,请立即安装。您可以搜索获取最新链接,请确保安装适合您 SQL Server 版本的。我喜欢使用这个数据库,因为它提供了丰富的数据集供您操作,而无需安装太多其他东西。
扁平化解决方案
我们将查看 salesorderdetail 表,并按月和年获取价格总和。用普通 SQL 获取结果如下:
select year(modifieddate) as year, month(modifieddate) as month, sum(UnitPrice) as unitprice
from sales.SalesOrderDetail
group by year (modifieddate), month(modifieddate)
order by year(modifieddate), month(modifieddate)
这在很多情况下都是完全可行的。但是,我们想要一种格式,其中每一行代表一年,月份横跨该行,每个月份一个列。
PIVOT
我们首先要看的是 PIVOT 关键字。它用于将一组数据“旋转”90 度,如果您愿意这么说,这样原本是行的内容就会变成数据中的列。这才是主要的方法,其他方法通常都不如它好。
这是 PIVOT 代码
select [year], isnull([1], 0) as 'Jan', isnull([2], 0) as 'Feb', isnull([3], 0) as 'Mar', isnull([4], 0) as 'Apr', isnull([5], 0) as 'May', isnull([6], 0) as 'Jun', isnull([7], 0) as 'Jul', isnull([8], 0) as 'Aug', isnull([9], 0) as 'Sep', isnull([10], 0) as 'Oct', isnull([11], 0) as 'Nov', isnull([12], 0) as 'Dec'
from
(
select UnitPrice as unitprice, year(modifieddate) as year, month(modifieddate) as month from sales.SalesOrderDetail
) as src
pivot
(
sum(unitprice) for month in ([1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12])
) as pvt
order by [year];
首先,运行它。您会发现它看起来更好,尤其是在比较不同年份的同一月份时。语法起初可能看起来令人困惑,所以我们来一起看看。
核心语句是这个
select UnitPrice as unitprice, year(modifieddate) as year, month(modifieddate) as month from sales.SalesOrderDetail
它返回的数据如下
unitprice year month 461.694 2008 6 600.2625 2006 9 36.83 2007 8 3399.99 2005 9 31.584 2007 9 32.9945 2006 9 3374.99 2006 5 21.98 2007 10 2024.994 2005 8 40.5942 2007 7 818.70 2006 2 ....
但带有重复项(因为同一个物品在一个月内被订购了不止一次)。
主 SELECT 语句如下
select [year], isnull([1], 0) as 'Jan', isnull([2], 0) as 'Feb', isnull([3], 0) as 'Mar', isnull([4], 0) as 'Apr', isnull([5], 0) as 'May', isnull([6], 0) as 'Jun', isnull([7], 0) as 'Jul', isnull([8], 0) as 'Aug', isnull([9], 0) as 'Sep', isnull([10], 0) as 'Oct', isnull([11], 0) as 'Nov', isnull([12], 0) as 'Dec'
IsNull 语句只是将 null 值转换为 0,使其更易读。如果您想将“没有数据”与“数据为 0”区分开,可以删除它们。然后我们读取名为 1 到 12 的列,并将它们重命名为相应月份的名称。但是,我们根本不选择这些值。它们来自 PIVOT 语句,即这个
pivot
(
sum(unitprice) for month in ([1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12])
) as pvt
因此,PIVOT 的作用是获取核心语句中的数据,并选择要 PIVOT 的内容。在这种情况下,它将根据月份值进行 PIVOT,并创建值为 1-12 的列。注意 - 您必须知道您的最终值的完整列表,您不能这样做
pivot
(
sum(unitprice) for month in (select id from months)
) as pvt
稍后会详细介绍,但这是我将在最后一个方法之前介绍的所有方法的主要弱点。
因此,年份值既没有列为 PIVOT 值,也没有列为聚合值,所以它只是被传递了。月份成为求和操作的窗口,从这个意义上说,它与我们最初的语句做的事情相同。令人高兴的是,年份是被考虑在内的,如果没有,我们所有行的值都会相同。因此,您添加的任何行都会创建窗口,而不仅仅是您 PIVOT 的那一行。
所以,PIVOT 包括以下内容:
- 获取所需数据的核心语句
- 定义要相对于 PIVOT 返回的值、要 PIVOT 的值以及可能值的范围的 PIVOT 语句
- 选择您想要的来自核心语句和 PIVOT 值的组合的 SELECT 语句。
这确实并不难,但需要一些时间来适应。我建议您花一些时间去练习,想出一些 PIVOT 数据的想法,然后找出如何实现它们。一个建议是 - 如果您想 PIVOT 这个例子,但使用季度数据呢?
使用 CASE
您可以通过编写自己的 CASE 语句来创建列并让它们相应地聚合,从而重现 PIVOT 的功能。这相当直接,这里是示例
SELECT YEAR(modifieddate) AS OrderYear,
SUM(CASE WHEN DATEPART(m, modifieddate) = 1
THEN unitPrice ELSE 0 END) AS 'Jan',
SUM(CASE WHEN DATEPART(m, modifieddate) = 2
THEN unitPrice ELSE 0 END) AS 'Feb',
SUM(CASE WHEN DATEPART(m, modifieddate) = 3
THEN unitPrice ELSE 0 END) AS 'Mar',
SUM(CASE WHEN DATEPART(m, modifieddate) = 4
THEN unitPrice ELSE 0 END) AS 'Apr',
SUM(CASE WHEN DATEPART(m, modifieddate) = 5
THEN unitPrice ELSE 0 END) AS 'May',
SUM(CASE WHEN DATEPART(m, modifieddate) = 6
THEN unitPrice ELSE 0 END) AS 'Jun',
SUM(CASE WHEN DATEPART(m, modifieddate) = 7
THEN unitPrice ELSE 0 END) AS 'Jul',
SUM(CASE WHEN DATEPART(m, modifieddate) = 8
THEN unitPrice ELSE 0 END) AS 'Aug',
SUM(CASE WHEN DATEPART(m, modifieddate) = 9
THEN unitPrice ELSE 0 END) AS 'Sep',
SUM(CASE WHEN DATEPART(m, modifieddate) = 10
THEN unitPrice ELSE 0 END) AS 'Oct',
SUM(CASE WHEN DATEPART(m, modifieddate) = 11
THEN unitPrice ELSE 0 END) AS 'Nov',
SUM(CASE WHEN DATEPART(m, modifieddate) = 12
THEN unitPrice ELSE 0 END) AS 'Dec'
FROM sales.SalesOrderDetail
GROUP BY DATEPART(yyyy, modifieddate)
order by orderyear;
因此,我们对每一列使用 SUM 语句,并在其中放入一个 CASE 语句,仅聚合该月份的值,并使用 GROUP BY 定义每个 SUM 语句的窗口。请注意,由于 SQL 查询中的操作顺序,当我们的 GROUP BY 发生时,列尚不存在,所以我不能按 ORDERYEAR 分组,我必须再次使用 datepart 语句。另请注意,year(modifieddate) 和 datepart(yyyy, modifieddate) 是相同的。
David Rozenshtein 方法
这是我在搜索以确保本文尽可能完整时偶然发现的一种方法。David 似乎是 Transact SQL 书籍的作者,所以我认为这种方法出现在他的一本书中。它基本上使用一些技巧来实现与 CASE 语句相同的功能,但避免了 CASE 语句。这是它的样子
SELECT YEAR(modifieddate) AS OrderYear,
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 1)))) AS 'Jan',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 2)))) AS 'Feb',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 3)))) AS 'Mar',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 4)))) AS 'Apr',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 5)))) AS 'May',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 6)))) AS 'Jun',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 7)))) AS 'Jul',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 8)))) AS 'Aug',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 9)))) AS 'Sep',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 10)))) AS 'Oct',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 11)))) AS 'Nov',
SUM(unitPrice * (1 - ABS(SIGN(MONTH(modifieddate) - 12)))) AS 'Dec'
FROM sales.SalesOrderDetail
GROUP BY YEAR(modifieddate)
order by orderyear;
基本上,它利用了 MONTH 函数返回的日期的一些技巧(注意,原始文章使用了 DATEPART,但我认为它已经足够难读了)。最终结果是,对于一月份,unitprice 将乘以 1(如果月份是一月),否则乘以 0。
我在 SQL 文件中添加了一些代码供您运行,如下所示
select (1 - ABS(SIGN(2 - 1)))
select (1 - ABS(SIGN(2 - 2)))
select (1 - ABS(SIGN(2 - 3)))
这将返回 0, 1, 0。它在做什么? Sign 函数对于小于 0 的值返回 -1,对于 0 返回 0,对于大于 0 的值返回 1。所以,sign(-12) 返回 -1。sign(12) 返回 1。ABS 返回绝对值。这意味着负号被去掉了。所以,abs(sign(xxx)) 如果 xxx 为零,则返回 0,否则返回 1。因此,unitPrice * (1 - abs(sign(xxx)) 表示 unitPrice * 1 如果 xxx 为 0,以及 unitPrice * 1 如果 xxx 不为 0。基本上就是这样。对我来说,这有点“聪明”,我不确定(我承认我没有做任何测试)SQL Server 在执行这种 RBAR(逐行)数学计算方面是否比逐行 CASE 语句更快。
使用矩阵表
这里的想法很简单。创建一个表,该表创建一个查找表,为您提供上一个解决方案正在计算的 1 和 0,并产生相同的结果。这是创建表的 SQL
CREATE TABLE MonthMatrix (
month_nbr INT NOT NULL PRIMARY KEY
CHECK (month_nbr BETWEEN 1 AND 12),
jan INT NOT NULL DEFAULT 0
CHECK (jan IN (0, 1)),
feb INT NOT NULL DEFAULT 0
CHECK (feb IN (0, 1)),
mar INT NOT NULL DEFAULT 0
CHECK (mar IN (0, 1)),
apr INT NOT NULL DEFAULT 0
CHECK (apr IN (0, 1)),
may INT NOT NULL DEFAULT 0
CHECK (may IN (0, 1)),
jun INT NOT NULL DEFAULT 0
CHECK (jun IN (0, 1)),
jul INT NOT NULL DEFAULT 0
CHECK (jul IN (0, 1)),
aug INT NOT NULL DEFAULT 0
CHECK (aug IN (0, 1)),
sep INT NOT NULL DEFAULT 0
CHECK (sep IN (0, 1)),
oct INT NOT NULL DEFAULT 0
CHECK (oct IN (0, 1)),
nov INT NOT NULL DEFAULT 0
CHECK (nov IN (0, 1)),
dec INT NOT NULL DEFAULT 0
CHECK (dec IN (0, 1)));
-- Populate the matrix table
INSERT INTO MonthMatrix (month_nbr, jan) VALUES (1, 1);
INSERT INTO MonthMatrix (month_nbr, feb) VALUES (2, 1);
INSERT INTO MonthMatrix (month_nbr, mar) VALUES (3, 1);
INSERT INTO MonthMatrix (month_nbr, apr) VALUES (4, 1);
INSERT INTO MonthMatrix (month_nbr, may) VALUES (5, 1);
INSERT INTO MonthMatrix (month_nbr, jun) VALUES (6, 1);
INSERT INTO MonthMatrix (month_nbr, jul) VALUES (7, 1);
INSERT INTO MonthMatrix (month_nbr, aug) VALUES (8, 1);
INSERT INTO MonthMatrix (month_nbr, sep) VALUES (9, 1);
INSERT INTO MonthMatrix (month_nbr, oct) VALUES (10, 1);
INSERT INTO MonthMatrix (month_nbr, nov) VALUES (11, 1);
INSERT INTO MonthMatrix (month_nbr, dec) VALUES (12, 1);
一旦您有了它,就可以这样做
SELECT DATEPART(yyyy, modifieddate) AS OrderYear,
SUM(UnitPrice * jan) AS 'Jan',
SUM(UnitPrice * feb) AS 'Feb',
SUM(UnitPrice * mar) AS 'Mar',
SUM(UnitPrice * apr) AS 'Apr',
SUM(UnitPrice * may) AS 'May',
SUM(UnitPrice * jun) AS 'Jun',
SUM(UnitPrice * jul) AS 'Jul',
SUM(UnitPrice * Aug) AS 'Aug',
SUM(UnitPrice * Sep) AS 'Sep',
SUM(UnitPrice * Oct) AS 'Oct',
SUM(UnitPrice * Nov) AS 'Nov',
SUM(UnitPrice * Dec) AS 'Dec'
FROM Sales.SalesOrderDetail AS S
JOIN MonthMatrix AS M
ON DATEPART(m, S.modifieddate) = M.month_nbr
GROUP BY DATEPART(yyyy, modifieddate)
order by orderyear;
希望它的工作原理显而易见。它一定比之前的两种解决方案都快,因为它完全是基于集合的。
我可以看到它用于月份和季度,但我无法想象为我想要 PIVOT 的每组可能值创建一个矩阵表。
使用 OUTER APPLY
CROSS APPLY 运算符调用 table1 的每一行来匹配 table2 的每一行。因此,它的成本是指数级的。这也意味着添加要 PIVOT 的列的成本是指数级的。我永远不推荐这样做,我在网上找到的示例在 3 个值上进行了 PIVOT。这个在 12 个值上进行 PIVOT 的 SQL 运行了 16 小时,然后我放弃并停止了它。
SELECT distinct DATEPART(yyyy, modifieddate), D1.[Jan], D2.[Feb], D3.[Mar], D4.[Apr], D5.[May], D6.[Jun], D7.[Jul],
D8.Aug, D9.Sep, D10.Oct, D11.Nov, D12.[Dec], T.Total
FROM Sales.SalesOrderDetail SOD
OUTER APPLY (SELECT DATEPART(yyyy, modifieddate) as [year], SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Jan]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 1) D1
OUTER APPLY (SELECT DATEPART(yyyy, modifieddate) as [year], SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Feb]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 2) D2
OUTER APPLY (SELECT DATEPART(yyyy, modifieddate) as [year], SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Mar]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 3) D3
OUTER APPLY (SELECT SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Apr]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 4) D4
OUTER APPLY (SELECT SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [May]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 5) D5
OUTER APPLY (SELECT SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Jun]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 6) D6
OUTER APPLY (SELECT SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Jul]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 7) D7
OUTER APPLY (SELECT SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Aug]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 8) D8
OUTER APPLY (SELECT SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Sep]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 9) D9
OUTER APPLY (SELECT SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Oct]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 10) D10
OUTER APPLY (SELECT SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Nov]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 11) D11
OUTER APPLY (SELECT SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS [Dec]
from Sales.SalesOrderDetail s1
where month(modifieddate) = 12) D12
OUTER APPLY (SELECT SUM(UnitPrice) over (partition by DATEPART(yyyy, modifieddate)) AS Total
FROM Sales.SalesOrderDetail S
WHERE year(S.modifieddate) = year(sod.ModifiedDate)
) t
到目前为止的故事
因此,我们研究了几种执行相同操作的可能方法,而所有其他方法实际上都是避免使用 PIVOT 关键字的方法,我认为这没有好理由,除非您使用的是 SS2005 之前的数据库。然而,“哪种方法最好”这个问题的答案通常是“视情况而定”,所以我尝试找到一些替代方案并展示它们,因为我确信有些时候这些方法(甚至包括 OUTER APPLY)是最佳选择。然而,它们都存在同一个基本问题。您需要提前知道您的 PIVOT 值是什么。这对于基于时间的查询来说是没问题的(永远不会有第五季度、第 13 个月或 32 天)。但是,对于其他类型的数据,例如用户名,这是一个严重的限制。我看到在线文章中“解决”此问题的方法是使用 SELECT 构建 SQL 字符串的存储过程,然后使用 EXEC 执行该字符串连接的 SQL。我永远不会这样做。如果存储过程只是字符串拼接和执行半任意 SQL,那么我看不出使用存储过程的意义。但是,我承认,如果您想要每列一个值,这可能是唯一的实现方式,而且是针对任意值列表。
使用 FOR XML
但是,如果您可以接受将所有值压缩到一个列中,那么有一个解决方案。我见过这个解决方案以“一键复制粘贴”的方式在网络上广泛传播。我想带您一步步了解它,以便您了解它在做什么。所以,请不要跳到最后,请耐心等待,并按顺序运行我给您的每个语句,以便您了解它们在做什么。
首先,请运行此
SELECT distinct month(modifieddate)
FROM sales.SalesOrderDetail s
FOR XML PATH('month')
你得到
XML_F52E2B61-18A1-11d1-B105-00805F49916B
<month>9</month><month>3</month><month>12</month><month>6</month><month>7</month><month>1</month><month>10</month><month>4</month><month>5</month><month>2</month><month>11</month><month>8</month>
请注意,您的列有一个随机名称,月份 ID 以“month”的 XML 节点形式出现。这是一个 XML 片段,它不是有效的 XML,因为它没有根节点。
现在运行此
SELECT distinct convert(varchar(2), month(modifieddate))
FROM sales.SalesOrderDetail s
FOR XML PATH('')
您会得到类似这样的结果
XML_F52E2B61-18A1-11d1-B105-00805F49916B
312982611171054
有趣的是,当您删除节点名称时,顺序会改变,但我们在这里看到的是,您可以要求 XML 而不指定节点名称,您只会得到没有 XML 节点的字符串。
现在试试这个
SELECT distinct ', ' +convert(varchar(2), month(modifieddate))
FROM sales.SalesOrderDetail s
FOR XML PATH('')
这会返回:
XML_F52E2B61-18A1-11d1-B105-00805F49916B
, 11, 1, 8, 3, 6, 9, 4, 2, 10, 5, 12, 7
这不足为奇,但显然我们现在可以看到这开始对我们有用。
现在让我们尝试一些不同的东西。运行此
select stuff(', 1, 2, 3, 4, 5, 6', 1, 2, '')
STUFF 函数允许您指定要删除的字符串的开始位置和长度,以及要替换它的字符串。
这对我来说似乎有点晦涩难懂,但它适合我们的目的。获取一个我们能够生成的格式的字符串,并从第一个序数位置截取一个 2 个字符的字符串,并将其替换为空字符串,这给了我们这个
1, 2, 3, 4, 5, 6
所以,如果我们把这两个想法结合起来,我们可以运行这个
SELECT
distinct year(modifieddate),
STUFF
(
(
SELECT ',' + convert(varchar(2), month(modifieddate))
FROM sales.SalesOrderDetail s
WHERE year(s.modifieddate) = year(so.ModifiedDate)
group BY month(modifieddate)
order by month(modifieddate)
FOR XML PATH('')
), 1, 1, ''
) AS Months
FROM
sales.SalesOrderDetail so order by year(modifieddate)
这给了我们一个年份的月份列表。这些是我们有值的月份。现在让我们让它有用起来
SELECT distinct year(modifieddate),
STUFF
(
(
SELECT ',' + convert(varchar(2), month(modifieddate)) + ': ' + convert(varchar(10), sum(UnitPrice))
FROM sales.SalesOrderDetail s
WHERE year(s.modifieddate) = year(so.ModifiedDate)
group BY month(modifieddate)
order by month(modifieddate)
FOR XML PATH('')
), 1, 1, ''
) AS Months
FROM
sales.SalesOrderDetail so order by year(modifieddate)
这比 PIVOT 慢得多。结果看起来像这样
Year Months
2005 7: 704154.94,8: 1101956.11,9: 913860.60,10: 851378.61,11: 1376044.03,12: 1345397.82
2006 1: 901717.40,2: 1327442.35,3: 1208028.43,4: 1025423.92,5: 1520124.39,6: 1129626.03,7: 1104691.22,8: 1650825.53,9: 1148641.00,10: 952477.60,11: 1302360.94,12: 1282373.57
2007 1: 860448.52,2: 1302379.28,3: 1032358.48,4: 1042209.28,5: 1486359.64,6: 1217570.78,7: 1653869.56,8: 2135760.74,9: 2302144.85,10: 1767180.26,11: 2348285.76,12: 2981398.52
2008 1: 1942234.14,2: 2459396.65,3: 2537894.73,4: 2291712.32,5: 2994454.75,6: 3170724.26,7: 50840.63
请注意,为了能够连接它们,我必须将我的值转换为 varchar,但我能够在此中使用 SUM 等内容,并生成了我想要的确切数据。我故意在逗号后面没有加空格,以表明我可以更改 STUFF 的参数,以便删除任何长度的前导字符串。
在这种情况下,我认为 PIVOT 既更快,也更方便。所以,让我们看一个更好的例子。我想要获取每个订单的所有产品列表。希望在看了前面的例子之后,您可以看到这个例子在做什么。
select s.SalesOrderId,
stuff
(
(
SELECT ';' + p.Name
from Production.Product p
inner join sales.SalesOrderDetail so on so.ProductId = p.ProductId
where s.salesorderid = so.SalesOrderID
FOR XML PATH('')
), 1, 1, ''
) as Products
from
sales.SalesOrderDetail s group by salesorderid
order by salesorderId
由于产品名称中包含逗号,我使用了分号作为分隔符。这是输出示例
SalesOrderId | 产品 |
43659 | Mountain-100 Black, 42;Mountain-100 Black, 44;Mountain-100 Black, 48;Mountain-100 Silver, 38;Mountain-100 Silver, 42;Mountain-100 Silver, 44;Mountain-100 Silver, 48;Long-Sleeve Logo Jersey, M;Long-Sleeve Logo Jersey, XL;Mountain Bike Socks, M;AWC Logo Cap;Sport-100 Helmet, Blue |
43660 | Road-650 Red, 44;Road-450 Red, 52 |
43661 | HL Mountain Frame - Black, 48;HL Mountain Frame - Black, 42;HL Mountain Frame - Black, 38;AWC Logo Cap;Long-Sleeve Logo Jersey, L;HL Mountain Frame - Silver, 46;Mountain-100 Black, 38;Mountain-100 Black, 48;Sport-100 Helmet, Blue;HL Mountain Frame - Silver, 48;Mountain-100 Black, 42;Mountain-100 Silver, 44;Long-Sleeve Logo Jersey, XL;Mountain-100 Black, 44;Sport-100 Helmet, Black |
43662 | Road-650 Red, 52;Road-650 Black, 52;LL Road Frame - Red, 62;Road-450 Red, 58;LL Road Frame - Red, 44;Road-650 Red, 44;Road-650 Black, 58;Road-650 Black, 44;Road-150 Red, 56;Road-450 Red, 44;Road-650 Red, 48;ML Road Frame - Red, 48;Road-450 Red, 52;LL Road Frame - Red, 60;LL Road Frame - Black, 58;Road-150 Red, 62;Road-650 Red, 60;LL Road Frame - Red, 48;ML Road Frame - Red, 52;LL Road Frame - Black, 52;Road-650 Black, 60;Road-450 Red, 60 |
43663 | Road-650 Red, 60 |
43664 | Mountain-100 Silver, 42;Mountain-100 Black, 38;Long-Sleeve Logo Jersey, M;Long-Sleeve Logo Jersey, XL;Mountain-100 Black, 44;Mountain-100 Silver, 38;Mountain-100 Silver, 44;Mountain-100 Black, 48 |
43665 | Sport-100 Helmet, Blue;Mountain-100 Silver, 44;Sport-100 Helmet, Red;Long-Sleeve Logo Jersey, L;Mountain-100 Black, 44;AWC Logo Cap;Mountain-100 Black, 38;Mountain-100 Black, 48;Mountain Bike Socks, M;Mountain-100 Black, 42 |
43666 | Road-650 Red, 52;Road-150 Red, 56;ML Road Frame - Red, 48;Road-450 Red, 44;Road-650 Black, 44;Road- 650 Black, 60 |
43667 | Mountain Bike Socks, L;Mountain-100 Silver, 44;Mountain-100 Black, 48;Mountain-100 Black, 38 |
43668 | Road-450 Red, 44;Road-150 Red, 56;Road-650 Red, 60;Road-650 Black, 58;Long-Sleeve Logo Jersey, L;LL Road Frame - Red, 62;Sport-100 Helmet, Red;Sport-100 Helmet, Blue;Road-450 Red, 58;AWC Logo Cap;LL Road Frame - Red, 60;Road-450 Red, 60;Road-650 Red, 62;Road-650 Black, 52;LL Road Frame - Red, 48;Road-650 Red, 52;Road-650 Black, 60;LL Road Frame - Red, 44;Long-Sleeve Logo Jersey, XL;Road-650 Black, 44;ML Road Frame - Red, 48;Road-450 Red, 52;Road-650 Red, 44;LL Road Frame - Black, 52;Long-Sleeve Logo Jersey, M;LL Road Frame - Black, 58;Sport-100 Helmet, Black;ML Road Frame - Red, 52;Road-650 Red, 48 |
43669 | HL Mountain Frame - Black, 38 |
43670 | Mountain Bike Socks, L;Mountain Bike Socks, M;Mountain-100 Silver, 44;Mountain-100 Black, 42 |
43671 | Road-150 Red, 56;Long-Sleeve Logo Jersey, M;Road-450 Red, 44;Road-650 Black, 44;ML Road Frame - Red, 48;Road-650 Red, 48;Road-450 Red, 60;Road-650 Red, 52;Long-Sleeve Logo Jersey, XL;Sport-100 Helmet, Blue;Sport-100 Helmet, Black |
43672 | Mountain Bike Socks, M;Mountain-100 Black, 42;Mountain-100 Silver, 48 |
43673 | Road-450 Red, 58;Long-Sleeve Logo Jersey, L;LL Road Frame - Red, 60;Road-650 Red, 62;Road-650 Red, 44;LL Road Frame - Black, 52;Road-650 Black, 52;AWC Logo Cap;Sport-100 Helmet, Black;Sport-100 Helmet, Blue;Sport-100 Helmet, Red |
43674 | Road-450 Red, 52 |
结论
希望您觉得本文有所帮助。我知道我曾认为 PIVOT 很难使用,而且六个月前,我完全不知道如何轻松地从 SQL Server 获取任意列表。如果 PIVOT 可以使用 SELECT 来获取要 PIVOT 的值列表,那就太好了,即使它内部不如我在线看到的字符串拼接 SQL 解决方案好。然而,基于 XML 的解决方案非常灵活且功能强大,前提是您不介意将所有数据都放在一个列中。
一如既往,我鼓励您提出关于 SQL 的任何问题,特别是关于本文中讨论的内容。如果您知道我遗漏了什么,请告诉我,以便我改进文章。我会在论坛上关注 SQL 问题(尽管大多数问题在我睡觉时就会得到解答),并为下周准备下一期...