Mat*_*tum 10 performance sql-server pivot query-performance
有两种类型的方法来执行PIVOT
. 在 SQL Server 2005PIVOT
推出之前,大多数人都是这样做的:
SELECT RateID
SUM(CASE WHEN RateItemTypeID = 1 THEN UnitPrice ELSE 0 END),
SUM(CASE WHEN RateItemTypeID = 2 THEN UnitPrice ELSE 0 END),
SUM(CASE WHEN RateItemTypeID = 3 THEN UnitPrice ELSE 0 END)
FROM rate_item WHERE _WhereClause_
GROUP BY RateID
Run Code Online (Sandbox Code Playgroud)
后来,当 2005 年推出时,PIVOT
它变成了这样:
SELECT RateID, [1], [2], [3]
FROM PertinentRates -- PertinentRates is a CTE with WHERE clause applied
PIVOT (SUM(UnitPrice) FOR RateItemTypeID IN ([1], [2], [3])) PVT)
Run Code Online (Sandbox Code Playgroud)
在 SQL Server 2005、2008 R2、2012 和 2014(我使用过该工具的 SQL Server 版本PIVOT
)中,根据我的经验,它总是比SUM(CASE)
或在某些情况下同样快。有没有PIVOT
更慢的例子?
我不能给出 DDL,因为它是我工作中的一个例子。但是表格很简单。在PIVOT
示例中,它是从 CTE 中绘制的,而 则SUM(CASE)
是直接从表中绘制的。但是SUM(CASE)
从 CTE 执行相同的绘图。
在我的工作示例中,PIVOT
10 秒SUM(CASE)
后返回,而 14秒后返回。显然它必须在幕后做一些不同的事情。计划相同,各占总数的 50%。在查询分析器中PIVOT
转换为SUM(CASE)
。然而,SUM(CASE)
在不到 13 秒内PIVOT
永远不会回来,在 11 秒内永远不会回来。
我试过来回运行它们,它们的运行顺序无关紧要。如果我从冷缓存中运行它们,它们都需要更长的时间,但PIVOT
仍然更快,12 秒 vs 17 秒。无法在第二台服务器上重现,但那台要好得多;每个有 5 秒,有细微的变化。PIVOT
好一点,但按百分比计算,它与第一台服务器上的优势不同。
与查询计划一样,IO 统计信息在两者之间是相同的。这很奇怪,我有点期望看到不同的 IO 统计数据,即使我从未在这个特定示例中查看过它们。
Pau*_*ite 23
有没有
PIVOT
更慢的例子?
这在简单的情况下是不可能的。正如 Itzik Ben-Gan 在他的 SQL Server Pro 文章“查看查询计划时透视数据”中所指出的那样PIVOT
(强调):
图 3 显示了
PIVOT
查询计划。如您所见,该计划与标准解决方案的计划非常相似,以至于如果您查看聚合运算符的属性,在定义的值下,您会发现 SQL ServerCASE
在幕后构造了表达式:...
[Expr1022] = Scalar Operator(SUM(CASE WHEN [InsideTSQL2008].[Sales].[Orders].[shipcity]=N'Barcelona' THEN [InsideTSQL2008].[Sales].[Orders].[freight] ELSE NULL END))
...考虑到这一点,您不应期望基于
PIVOT
运算符的解决方案的性能优于标准解决方案。PIVOT
操作员目前的主要好处是它不那么冗长。
对于(非标准)PIVOT
语法不直接支持的更高级的旋转要求,需要解决方法。与 相比,这些可能会也可能不会导致更差的性能CASE
,这取决于各种因素,包括实现者的技能水平。
Itzik 的文章中介绍了这些问题案例的示例,Robert Sheldon 的 Simple Talk 文章《关于在 SQL Server 中透视数据的问题你太害羞而不敢问》中也有很好的解释。
我的经验是,当两者都以最佳方式编写时PIVOT
,会Agg(CASE...
生成极其相似的具有极其接近的性能特征的计划。我通常的建议是使用您觉得最自然的任何语法编写查询,并且仅在性能不可接受的情况下才尝试重写。
SQL Server 查询处理器确实有一个内置的 Pivot逻辑运算符 ( LogOp_Pivot
),所以说 SQL Server 将Pivot重写为聚合和 case 表达式可能不太正确,至少如果我们谈论的是解析和编译活动放置在基于成本的优化之前(琐碎计划不适用于数据透视查询)。
另一方面,优化器实现包含查询树的唯一方法LogOp_Pivot
是通过探索规则ExpandPivot
。此规则扩展LogOp_Pivot
为LogOp_GbAgg
具有关联标量表达式的正常分组聚合 ( )。禁用此规则时,透视查询将无法编译。
在实践中,我们可以说在生成可执行计划之前,枢轴总是(最终)“重写”为聚合和标量表达式。
无论如何,rewrite to 的结果通过常规 group-by 聚合实现规则(hash) 或(stream)LogOp_GbAgg
转换为可执行计划所需的物理运算符。GbAggToHS
GbAggToStrm
作为旁注,“手动数据透视”(case 表达式上的聚合)在聚合下方有一个额外的计算标量的原因是,在 Project Normalization(编译的早期阶段,在基于成本的优化之前)。
使用该PIVOT
语法的查询没有这个,因为在ExpandPivot
基于成本的优化期间运行之前不会创建表达式。在(较早)项目规范化运行时,查询树仍然有LogOp_Pivot
元素,因此没有要下推的投影,并且 case 表达式通常会在哈希或流聚合内结束。
避免使用计算标量通常没有任何优势,因为从 SQL Server 2005 开始,表达式计算通常会推迟到后面的运算符需要结果为止。在这种情况下,case 表达式的计算被推迟到聚合(散列或流)需要它。
Sql*_*Zim 13
重复交叉表和数据透视表中的测试,第 1 部分 – 将行转换为列 - Jeff Moden, 2010/08/06 (首次发布:2008/08/19)在 reextester 上
不幸的是,我无法访问 rextester 上的 IO、时间或执行计划的统计信息,但它具有独特的优势,它是一个通用的测试环境,这里的任何人都可以修改和检查。我意识到这在深入挖掘和调查正在发生的事情方面仍有一些不足之处,但我认为能够共享测试环境是本次讨论的一个重要方面。
雷克斯特:http: //rextester.com/BAZMGJ69528
这个是为@MartinSmith 添加的,虽然查询是从同一篇文章中提取的,但它不在原始测试中,如下所示:
create table #timer (what varchar(64), ended datetime);
insert into #timer values ('Start',getdate());
go
SELECT TOP 400000 --<<Look! Change this number for testing different size tables
RowNum = IDENTITY(INT,1,1),
Company = CHAR(ABS(CHECKSUM(NEWID()))%2+65)
+ CHAR(ABS(CHECKSUM(NEWID()))%2+65)
+ CHAR(ABS(CHECKSUM(NEWID()))%2+65),
Amount = CAST(ABS(CHECKSUM(NEWID()))%1000000/100.0 AS MONEY),
Quantity = ABS(CHECKSUM(NEWID()))%50000+1,
Date = CAST(RAND(CHECKSUM(NEWID()))*3653.0+36524.0 AS DATETIME),
Year = CAST(NULL AS SMALLINT),
Quarter = CAST(NULL AS TINYINT)
INTO #SomeTable3
FROM Master.sys.SysColumns t1
CROSS JOIN
Master.sys.SysColumns t2
--===== Fill in the Year and Quarter columns from the Date column
UPDATE #SomeTable3
SET Year = DATEPART(yy,Date),
Quarter = DATEPART(qq,Date)
--===== A table is not properly formed unless a Primary Key has been assigned
-- Takes about 1 second to execute.
ALTER TABLE #SomeTable3
ADD PRIMARY KEY CLUSTERED (RowNum)
CREATE NONCLUSTERED INDEX IX_#SomeTable3_CoverYear
ON dbo.#SomeTable3 (Year)
INCLUDE (Amount, Quantity, Quarter)
create statistics syear on #sometable3(year) with fullscan, norecompute;
create statistics syearquarter on #sometable3(year,quarter) with fullscan, norecompute;
GO
insert into #timer values ('Finished Loading Test Data',getdate());
go
--===== Simple Pivot
SELECT Year,
COALESCE([1],0) AS [1st Qtr],
COALESCE([2],0) AS [2nd Qtr],
COALESCE([3],0) AS [3rd Qtr],
COALESCE([4],0) AS [4th Qtr],
COALESCE([1],0) + COALESCE([2] ,0) + COALESCE([3],0) + COALESCE([4],0) AS Total
into #SimplePivot_prep
FROM (SELECT Year, Quarter,Amount FROM #SomeTable3) AS src
PIVOT (SUM(Amount) FOR Quarter IN ([1],[2],[3],[4])) AS pvt
go
--===== Simple Cross Tab
SELECT Year,
SUM(CASE WHEN Quarter = 1 THEN Amount ELSE 0 END) AS [1st Qtr],
SUM(CASE WHEN Quarter = 2 THEN Amount ELSE 0 END) AS [2nd Qtr],
SUM(CASE WHEN Quarter = 3 THEN Amount ELSE 0 END) AS [3rd Qtr],
SUM(CASE WHEN Quarter = 4 THEN Amount ELSE 0 END) AS [4th Qtr],
SUM(Amount) AS Total
into #simpleCrossTab_prep
FROM #SomeTable3
GROUP BY Year
go
--insert into #timer values ('Simple Cross Tab',getdate());
go
--=====--
insert into #timer values ('Finished Prep',getdate());
go
--=====--
--===== Simple Pivot
SELECT Year,
COALESCE([1],0) AS [1st Qtr],
COALESCE([2],0) AS [2nd Qtr],
COALESCE([3],0) AS [3rd Qtr],
COALESCE([4],0) AS [4th Qtr],
COALESCE([1],0) + COALESCE([2] ,0) + COALESCE([3],0) + COALESCE([4],0) AS Total
into #SimplePivot
FROM (SELECT Year, Quarter,Amount FROM #SomeTable3) AS src
PIVOT (SUM(Amount) FOR Quarter IN ([1],[2],[3],[4])) AS pvt
go
insert into #timer values ('Simple Pivot',getdate());
go
--=====--
--===== Simple Cross Tab
SELECT Year,
SUM(CASE WHEN Quarter = 1 THEN Amount ELSE 0 END) AS [1st Qtr],
SUM(CASE WHEN Quarter = 2 THEN Amount ELSE 0 END) AS [2nd Qtr],
SUM(CASE WHEN Quarter = 3 THEN Amount ELSE 0 END) AS [3rd Qtr],
SUM(CASE WHEN Quarter = 4 THEN Amount ELSE 0 END) AS [4th Qtr],
SUM(Amount) AS Total
into #simpleCrossTab
FROM #SomeTable3
GROUP BY Year
go
insert into #timer values ('Simple Cross Tab',getdate());
go
--=====--
select
o.what
, started=isnull(convert(varchar(30),x.ended),o.ended)
, ended=convert(varchar(30),o.ended)
, DurationInMs=datediff(millisecond,x.ended,o.ended)
from #timer o
outer apply (select top 1 ended from #timer i where i.ended < o.ended order by i.ended desc) as x
Run Code Online (Sandbox Code Playgroud)
返回:
+----------------------------+---------------------+---------------------+--------------+
| what | started | ended | DurationInMs |
+----------------------------+---------------------+---------------------+--------------+
| Start | Feb 19 2017 7:13PM | Feb 19 2017 7:13PM | NULL |
| Finished Loading Test Data | Feb 19 2017 7:13PM | Feb 19 2017 7:13PM | 7210 |
| Finished Prep | Feb 19 2017 7:13PM | Feb 19 2017 7:13PM | 700 |
| Simple Pivot | Feb 19 2017 7:13PM | Feb 19 2017 7:13PM | 340 |
| Simple Cross Tab | Feb 19 2017 7:13PM | Feb 19 2017 7:13PM | 386 |
+----------------------------+---------------------+---------------------+--------------+
Run Code Online (Sandbox Code Playgroud)
其余的所有测试限制都在pivot
语法中,其中单个交叉表查询可以完成需要多个pivot
s 的查询。
雷克斯特:http: //rextester.com/UVZE87903
create table #timer (what varchar(64), ended datetime);
insert into #timer values ('Start',getdate());
go
SELECT TOP 300000 --<<Look! Change this number for testing different size tables
RowNum = IDENTITY(INT,1,1),
Company = CHAR(ABS(CHECKSUM(NEWID()))%2+65)
+ CHAR(ABS(CHECKSUM(NEWID()))%2+65)
+ CHAR(ABS(CHECKSUM(NEWID()))%2+65),
Amount = CAST(ABS(CHECKSUM(NEWID()))%1000000/100.0 AS MONEY),
Quantity = ABS(CHECKSUM(NEWID()))%50000+1,
Date = CAST(RAND(CHECKSUM(NEWID()))*3653.0+36524.0 AS DATETIME),
Year = CAST(NULL AS SMALLINT),
Quarter = CAST(NULL AS TINYINT)
INTO #SomeTable3
FROM Master.sys.SysColumns t1
CROSS JOIN
Master.sys.SysColumns t2
--===== Fill in the Year and Quarter columns from the Date column
UPDATE #SomeTable3
SET Year = DATEPART(yy,Date),
Quarter = DATEPART(qq,Date)
--===== A table is not properly formed unless a Primary Key has been assigned
-- Takes about 1 second to execute.
ALTER TABLE #SomeTable3
ADD PRIMARY KEY CLUSTERED (RowNum)
CREATE NONCLUSTERED INDEX IX_#SomeTable3_Cover1
ON dbo.#SomeTable3 (Company, Year)
INCLUDE (Amount, Quantity, Quarter)
create statistics scompanyyear on #sometable3(company, year) with fullscan, norecompute;
GO
insert into #timer values ('Finished Loading Test Data',getdate());
go
--=====--
--===== "Normal" Pivot
SELECT amt.Company,
amt.Year,
COALESCE(amt.[1],0) AS Q1Amt,
COALESCE(qty.[1],0) AS Q1Qty,
COALESCE(amt.[2],0) AS Q2Amt,
COALESCE(qty.[2],0) AS Q2Qty,
COALESCE(amt.[3],0) AS Q3Amt,
COALESCE(qty.[3],0) AS Q3Qty,
COALESCE(amt.[4],0) AS Q4Amt,
COALESCE(qty.[4],0) AS Q5Qty,
COALESCE(amt.[1],0)+COALESCE(amt.[2],0)+COALESCE(amt.[3],0)+COALESCE(amt.[4],0) AS TotalAmt,
COALESCE(qty.[1],0)+COALESCE(qty.[2],0)+COALESCE(qty.[3],0)+COALESCE(qty.[4],0) AS TotalQty
into #NormalPivot_prep
FROM (SELECT Company, Year, Quarter, Amount FROM #SomeTable3) t1
PIVOT (SUM(Amount) FOR Quarter IN ([1], [2], [3], [4])) AS amt
INNER JOIN
(SELECT Company, Year, Quarter, Quantity FROM #SomeTable3) t2
PIVOT (SUM(Quantity) FOR Quarter IN ([1], [2], [3], [4])) AS qty
ON qty.Company = amt.Company
AND qty.Year = amt.Year
ORDER BY amt.Company, amt.Year
go
--insert into #timer values ('Finished Normal Pivot',getdate());
go
--=====--
--===== "Normal" Cross Tab
SELECT Company,
Year,
SUM(CASE WHEN Quarter = 1 THEN Amount ELSE 0 END) AS Q1Amt,
SUM(CASE WHEN Quarter = 1 THEN Quantity ELSE 0 END) AS Q1Qty,
SUM(CASE WHEN Quarter = 2 THEN Amount ELSE 0 END) AS Q2Amt,
SUM(CASE WHEN Quarter = 2 THEN Quantity ELSE 0 END) AS Q2Qty,
SUM(CASE WHEN Quarter = 3 THEN Amount ELSE 0 END) AS Q3Amt,
SUM(CASE WHEN Quarter = 3 THEN Quantity ELSE 0 END) AS Q3Qty,
SUM(CASE WHEN Quarter = 4 THEN Amount ELSE 0 END) AS Q4Amt,
SUM(CASE WHEN Quarter = 4 THEN Quantity ELSE 0 END) AS Q4Qty,
SUM(Amount) AS TotalAmt,
SUM(Quantity) AS TotalQty
into #NormalCrossTab_prep
FROM #SomeTable3
GROUP BY Company, Year
ORDER BY Company, Year
go
--insert into #timer values ('Finished Normal Cross Tab',getdate());
insert into #timer values ('Finished Prep',getdate());
go
--=====--
--===== "Normal" Pivot
SELECT amt.Company,
amt.Year,
COALESCE(amt.[1],0) AS Q1Amt,
COALESCE(qty.[1],0) AS Q1Qty,
COALESCE(amt.[2],0) AS Q2Amt,
COALESCE(qty.[2],0) AS Q2Qty,
COALESCE(amt.[3],0) AS Q3Amt,
COALESCE(qty.[3],0) AS Q3Qty,
COALESCE(amt.[4],0) AS Q4Amt,
COALESCE(qty.[4],0) AS Q5Qty,
COALESCE(amt.[1],0)+COALESCE(amt.[2],0)+COALESCE(amt.[3],0)+COALESCE(amt.[4],0) AS TotalAmt,
COALESCE(qty.[1],0)+COALESCE(qty.[2],0)+COALESCE(qty.[3],0)+COALESCE(qty.[4],0) AS TotalQty
into #NormalPivot
FROM (SELECT Company, Year, Quarter, Amount FROM #SomeTable3) t1
PIVOT (SUM(Amount) FOR Quarter IN ([1], [2], [3], [4])) AS amt
INNER JOIN
(SELECT Company, Year, Quarter, Quantity FROM #SomeTable3) t2
PIVOT (SUM(Quantity) FOR Quarter IN ([1], [2], [3], [4])) AS qty
ON qty.Company = amt.Company
AND qty.Year = amt.Year
ORDER BY amt.Company, amt.Year
go
insert into #timer values ('Finished Normal Pivot',getdate());
go
--=====--
--===== "Normal" Cross Tab
SELECT Company,
Year,
SUM(CASE WHEN Quarter = 1 THEN Amount ELSE 0 END) AS Q1Amt,
SUM(CASE WHEN Quarter = 1 THEN Quantity ELSE 0 END) AS Q1Qty,
SUM(CASE WHEN Quarter = 2 THEN Amount ELSE 0 END) AS Q2Amt,
SUM(CASE WHEN Quarter = 2 THEN Quantity ELSE 0 END) AS Q2Qty,
SUM(CASE WHEN Quarter = 3 THEN Amount ELSE 0 END) AS Q3Amt,
SUM(CASE WHEN Quarter = 3 THEN Quantity ELSE 0 END) AS Q3Qty,
SUM(CASE WHEN Quarter = 4 THEN Amount ELSE 0 END) AS Q4Amt,
SUM(CASE WHEN Quarter = 4 THEN Quantity ELSE 0 END) AS Q4Qty,
SUM(Amount) AS TotalAmt,
SUM(Quantity) AS TotalQty
into #NormalCrossTab
FROM #SomeTable3
GROUP BY Company, Year
ORDER BY Company, Year
go
insert into #timer values ('Finished Normal Cross Tab',getdate());
go
--=====--
select
o.what
, started=isnull(convert(varchar(30),x.ended),o.ended)
, ended=convert(varchar(30),o.ended)
, DurationInMs=datediff(millisecond,x.ended,o.ended)
from #timer o
outer apply (select top 1 ended from #timer i where i.ended < o.ended order by i.ended desc) as x
Run Code Online (Sandbox Code Playgroud)
返回:
+----------------------------+---------------------+---------------------+--------------+
| what | started | ended | DurationInMs |
+----------------------------+---------------------+---------------------+--------------+
| Start | Feb 19 2017 7:19PM | Feb 19 2017 7:19PM | NULL |
| Finished Loading Test Data | Feb 19 2017 7:19PM | Feb 19 2017 7:19PM | 5260 |
| Finished Prep | Feb 19 2017 7:19PM | Feb 19 2017 7:19PM | 1003 |
| Finished Normal Pivot | Feb 19 2017 7:19PM | Feb 19 2017 7:19PM | 550 |
| Finished Normal Cross Tab | Feb 19 2017 7:19PM | Feb 19 2017 7:19PM | 513 |
+----------------------------+---------------------+---------------------+--------------+
Run Code Online (Sandbox Code Playgroud)
雷克斯特:http: //rextester.com/WBGUYR51251
create table #timer (what varchar(64), ended datetime);
insert into #timer values ('Start',getdate());
go
SELECT TOP 300000 --<<Look! Change this number for testing different size tables
RowNum = IDENTITY(INT,1,1),
Company = CHAR(ABS(CHECKSUM(NEWID()))%2+65)
+ CHAR(ABS(CHECKSUM(NEWID()))%2+65)
+ CHAR(ABS(CHECKSUM(NEWID()))%2+65),
Amount = CAST(ABS(CHECKSUM(NEWID()))%1000000/100.0 AS MONEY),
Quantity = ABS(CHECKSUM(NEWID()))%50000+1,
Date = CAST(RAND(CHECKSUM(NEWID()))*3653.0+36524.0 AS DATETIME),
Year = CAST(NULL AS SMALLINT),
Quarter = CAST(NULL AS TINYINT)
INTO #SomeTable3
FROM Master.sys.SysColumns t1
CROSS JOIN
Master.sys.SysColumns t2
--===== Fill in the Year and Quarter columns from the Date column
UPDATE #SomeTable3
SET Year = DATEPART(yy,Date),
Quarter = DATEPART(qq,Date)
--===== A table is not properly formed unless a Primary Key has been assigned
-- Takes about 1 second to execute.
ALTER TABLE #SomeTable3
ADD PRIMARY KEY CLUSTERED (RowNum)
CREATE NONCLUSTERED INDEX IX_#SomeTable3_Cover1
ON dbo.#SomeTable3 (Company, Year)
INCLUDE (Amount, Quantity, Quarter)
create statistics scompanyyear on #sometable3(company, year) with fullscan, norecompute;
GO
insert into #timer values ('Finished Loading Test Data',getdate());
go
--=====--
--===== "Pre-aggregated" Pivot
SELECT amt.Company,
amt.Year,
COALESCE(amt.[1],0) AS Q1Amt,
COALESCE(qty.[1],0) AS Q1Qty,
COALESCE(amt.[2],0) AS Q2Amt,
COALESCE(qty.[2],0) AS Q2Qty,
COALESCE(amt.[3],0) AS Q3Amt,
COALESCE(qty.[3],0) AS Q3Qty,
COALESCE(amt.[4],0) AS Q4Amt,
COALESCE(qty.[4],0) AS Q5Qty,
COALESCE(amt.[1],0)+COALESCE(amt.[2],0)+COALESCE(amt.[3],0)+COALESCE(amt.[4],0) AS TotalAmt,
COALESCE(qty.[1],0)+COALESCE(qty.[2],0)+COALESCE(qty.[3],0)+COALESCE(qty.[4],0) AS TotalQty
into #preA_Pivot_prep
FROM (SELECT Company, Year, Quarter, SUM(Amount) AS Amount FROM #SomeTable3 GROUP BY Company, Year, Quarter) t1
PIVOT (SUM(Amount) FOR Quarter IN ([1], [2], [3], [4])) AS amt
INNER JOIN
(SELECT Company, Year, Quarter, SUM(Quantity) AS Quantity FROM #SomeTable3 GROUP BY Company, Year, Quarter) t2
PIVOT (SUM(Quantity) FOR Quarter IN ([1], [2], [3], [4])) AS qty
ON qty.Company = amt.Company
AND qty.Year = amt.Year
ORDER BY amt.Company, amt.Year
go
--insert into #timer values ('Finished "Pre-aggregated" Pivot',getdate());
go
--=====--
--===== "Pre-aggregated" Cross Tab
SELECT Company,
Year,
SUM(CASE WHEN Quarter = 1 THEN Amount ELSE 0 END) AS Q1Amt,
SUM(CASE WHEN Quarter = 1 THEN Quantity ELSE 0 END) AS Q1Qty,
SUM(CASE WHEN Quarter = 2 THEN Amount ELSE 0 END) AS Q2Amt,
SUM(CASE WHEN Quarter = 2 THEN Quantity ELSE 0 END) AS Q2Qty,
SUM(CASE WHEN Quarter = 3 THEN Amount ELSE 0 END) AS Q3Amt,
SUM(CASE WHEN Quarter = 3 THEN Quantity ELSE 0 END) AS Q3Qty,
SUM(CASE WHEN Quarter = 4 THEN Amount ELSE 0 END) AS Q4Amt,
SUM(CASE WHEN Quarter = 4 THEN Quantity ELSE 0 END) AS Q4Qty,
SUM(Amount) AS TotalAmt,
SUM(Quantity) AS TotalQty
into #preA_CrossTab_prep
FROM (SELECT Company,Year,Quarter,SUM(Amount) AS Amount,SUM(Quantity) AS Quantity
FROM #SomeTable3 GROUP BY Company,Year,Quarter) d
GROUP BY Company, Year
ORDER BY Company, Year
go
--insert into #timer values ('Finished "Pre-aggregated" Cross Tab',getdate());
go
--=====--
insert into #timer values ('Finished Prep',getdate());
--=====--
--===== "Pre-aggregated" Pivot
SELECT amt.Company,
amt.Year,
COALESCE(amt.[1],0) AS Q1Amt,
COALESCE(qty.[1],0) AS Q1Qty,
COALESCE(amt.[2],0) AS Q2Amt,
COALESCE(qty.[2],0) AS Q2Qty,
COALESCE(amt.[3],0) AS Q3Amt,
COALESCE(qty.[3],0) AS Q3Qty,
COALESCE(amt.[4],0) AS Q4Amt,
COALESCE(qty.[4],0) AS Q5Qty,
COALESCE(amt.[1],0)+COALESCE(amt.[2],0)+COALESCE(amt.[3],0)+COALESCE(amt.[4],0) AS TotalAmt,
COALESCE(qty.[1],0)+COALESCE(qty.[2],0)+COALESCE(qty.[3],0)+COALESCE(qty.[4],0) AS TotalQty
into #preA_Pivot
FROM (SELECT Company, Year, Quarter, SUM(Amount) AS Amount FROM #SomeTable3 GROUP BY Company, Year, Quarter) t1
PIVOT (SUM(Amount) FOR Quarter IN ([1], [2], [3], [4])) AS amt
INNER JOIN
(SELECT Company, Year, Quarter, SUM(Quantity) AS Quantity FROM #SomeTable3 GROUP BY Company, Year, Quarter) t2
PIVOT (SUM(Quantity) FOR Quarter IN ([1], [2], [3], [4])) AS qty
ON qty.Company = amt.Company
AND qty.Year = amt.Year
ORDER BY amt.Company, amt.Year
go
insert into #timer values ('Finished "Pre-aggregated" Pivot',getdate());
go
--=====--
--===== "Pre-aggregated" Cross Tab
SELECT Company,
Year,
SUM(CASE WHEN Quarter = 1 THEN Amount ELSE 0 END) AS Q1Amt,
SUM(CASE WHEN Quarter = 1 THEN Quantity ELSE 0 END) AS Q1Qty,
SUM(CASE WHEN Quarter = 2 THEN Amount ELSE 0 END) AS Q2Amt,
SUM(CASE WHEN Quarter = 2 THEN Quantity ELSE 0 END) AS Q2Qty,
SUM(CASE WHEN Quarter = 3 THEN Amount ELSE 0 END) AS Q3Amt,
SUM(CASE WHEN Quarter = 3 THEN Quantity ELSE 0 END) AS Q3Qty,
SUM(CASE WHEN Quarter = 4 THEN Amount ELSE 0 END) AS Q4Amt,
SUM(CASE WHEN Quarter = 4 THEN Quantity ELSE 0 END) AS Q4Qty,
SUM(Amount) AS TotalAmt,
SUM(Quantity) AS TotalQty
into #preA_CrossTab
FROM (SELECT Company,Year,Quarter,SUM(Amount) AS Amount,SUM(Quantity) AS Quantity
FROM #SomeTable3 GROUP BY Company,Year,Quarter) d
GROUP BY Company, Year
ORDER BY Company, Year
go
insert into #timer values ('Finished "Pre-aggregated" Cross Tab',getdate());
go
--=====--
select
o.what
, started=isnull(convert(varchar(30),x.ended),o.ended)
, ended=convert(varchar(30),o.ended)
, DurationInMs=datediff(millisecond,x.ended,o.ended)
from #timer o
outer apply (select top 1 ended from #timer i where i.ended < o.ended order by i.ended desc) as x
Run Code Online (Sandbox Code Playgroud)
返回:
+-------------------------------------+---------------------+---------------------+--------------+
| what | started | ended | DurationInMs |
+-------------------------------------+---------------------+---------------------+--------------+
| Start | Feb 19 2017 7:23PM | Feb 19 2017 7:23PM | NULL |
| Finished Loading Test Data | Feb 19 2017 7:23PM | Feb 19 2017 7:23PM | 5440 |
| Finished Prep | Feb 19 2017 7:23PM | Feb 19 2017 7:23PM | 1513 |
| Finished "Pre-aggregated" Pivot | Feb 19 2017 7:23PM | Feb 19 2017 7:23PM | 683 |
| Finished "Pre-aggregated" Cross Tab | Feb 19 2017 7:23PM | Feb 19 2017 7:23PM | 370 |
+-------------------------------------+---------------------+---------------------+--------------+
Run Code Online (Sandbox Code Playgroud)
雷克斯特:http: //rextester.com/WCTJH5484
create table #timer (what varchar(64), ended datetime);
insert into #timer values ('Start',getdate());
go
SELECT TOP 300000 --<<Look! Change this number for testing different si
只是纠正错误假设的注释:
编写枢轴的主要方法有3 种,而不是两种。第三种是使用驱动表和多个连接(使用LEFT JOIN
或OUTER/CROSS APPLY
):
这可能或多或少有效,具体取决于几个细节(表分布、索引等)和特定数据透视操作的要求。它与 SUM / GROUP BY 方法有几个不同点:
如果有适当的索引(适当的意思:根据WHERE
子句、GROUP BY
子句和要聚合的列而不同),则可以避免扫描整个表。在特定示例中,(RateItemTypeID, RateID) INCLUDE (UnitPrice)
if上的索引_WherClause_
为空。
RateItemTypeID
值,但我们的查询只对其中几个感兴趣。扫描整个表(甚至整个索引)与寻找更窄 NCI 的一小部分相比,我希望第二个更有效。 WHERE
谓词和聚合列提供不同的索引。的GROUP BY
乃至整个驱动子查询可以经常由另一表(一取代Rate
在该特定示例表)。
在几个枢轴变体上GROUP BY
,子查询中的 也可以删除,子查询转换为简单LEFT
连接(例如,如果在特定情况下存在UNIQUE
约束(RateID, RateItemTypeID)
)。这表明SUM
在“SUM / GROUP BY”方法中(在这些情况下)仅因为GROUP BY
对一个值(和几个空值)求和。
查询:
SELECT
d.RateID,
Sum1 = COALESCE(s1.Sum1, 0),
Sum2 = COALESCE(s2.Sum2, 0),
Sum3 = COALESCE(s3.Sum3, 0)
FROM
( SELECT RateID --
FROM rate_item
WHERE _WhereClause_
GROUP BY RateID
) AS d -- driving table with the DISTINCT RateID values
OUTER APPLY
( SELECT Sum1 = SUM(r1.UnitPrice)
FROM rate_item AS r1
WHERE _WhereClause_
AND r1.RateItemTypeID = 1
AND r1.RateID = d.RateID
) AS s1
OUTER APPLY
( SELECT Sum2 = SUM(r2.UnitPrice)
FROM rate_item AS r2
WHERE _WhereClause_
AND r2.RateItemTypeID = 2
AND r2.RateID = d.RateID
) AS s2
OUTER APPLY
( SELECT Sum3 = SUM(r3.UnitPrice)
FROM rate_item AS r3
WHERE _WhereClause_
AND r3.RateItemTypeID = 3
AND r3.RateID = d.RateID
) AS s3 ;
Run Code Online (Sandbox Code Playgroud)
SQL Server 转换以下查询:
SELECT
RateID, [1], [2], [3]
FROM PertinentRates
PIVOT (SUM(UnitPrice) FOR RateItemTypeID IN ([1], [2], [3])) PVT)
Run Code Online (Sandbox Code Playgroud)
到:
SELECT
RateID
SUM(CASE WHEN RateItemTypeID = 1 THEN UnitPrice ELSE 0 END),
SUM(CASE WHEN RateItemTypeID = 2 THEN UnitPrice ELSE 0 END),
SUM(CASE WHEN RateItemTypeID = 3 THEN UnitPrice ELSE 0 END)
FROM rate_item WHERE supplierid = 2882874 AND rateplanid = 1 AND rateitemtypeid IN (1, 2, 3)
GROUP BY RateID
Run Code Online (Sandbox Code Playgroud)
所以选择一个而不是另一个,AFAIK 归结为可读性
下面是简短的演示:
CREATE TABLE #Sales (EmpId INT, Yr INT, Sales MONEY)
INSERT #Sales VALUES(1, 2005, 12000)
INSERT #Sales VALUES(1, 2006, 18000)
INSERT #Sales VALUES(1, 2007, 25000)
INSERT #Sales VALUES(2, 2005, 15000)
INSERT #Sales VALUES(2, 2006, 6000)
INSERT #Sales VALUES(3, 2006, 20000)
INSERT #Sales VALUES(3, 2007, 24000)
Run Code Online (Sandbox Code Playgroud)
现在查询
SELECT EmpId, [2005], [2006], [2007]
FROM (SELECT EmpId, Yr, Sales FROM #Sales) AS s
PIVOT (SUM(Sales) FOR Yr IN ([2005], [2006], [2007])) AS p
select
empid,
sum(Case when yr=2005 then sales end) '2005',
sum(Case when yr=2006 then sales end) '2006',
sum(Case when yr=2007 then sales end) '2007'
from
#sales
group by empid
Run Code Online (Sandbox Code Playgroud)
现在,当两个查询批量执行时,两者占用相同的成本并且计划大致相同:
的SHOWPLAN_TEXT
是
|--Compute Scalar(DEFINE:([Expr1003]=CASE WHEN [Expr1018]=(0) THEN NULL ELSE [Expr1019] END, [Expr1004]=CASE WHEN [Expr1020]=(0) THEN NULL ELSE [Expr1021] END, [Expr1005]=CASE WHEN [Expr1022]=(0) THEN NULL ELSE [Expr1023] END))
|--Stream Aggregate(GROUP BY:([tempdb].[dbo].[#Sales].[EmpId]) DEFINE:([Expr1018]=COUNT_BIG(CASE WHEN [tempdb].[dbo].[#Sales].[Yr]=(2005) THEN [tempdb].[dbo].[#Sales].[Sales] ELSE NULL END), [Expr1019]=SUM(CASE WHEN [tempdb].[dbo].[#Sales].[Yr]=(2005) THEN [tempdb].[dbo].[#Sales].[Sales] ELSE NULL END), [Expr1020]=COUNT_BIG(CASE WHEN [tempdb].[dbo].[#Sales].[Yr]=(2006) THEN [tempdb].[dbo].[#Sales].[Sales] ELSE NULL END), [Expr1021]=SUM(CASE WHEN [tempdb].[dbo].[#Sales].[Yr]=(2006) THEN [tempdb].[dbo].[#Sales].[Sales] ELSE NULL END), [Expr1022]=COUNT_BIG(CASE WHEN [tempdb].[dbo].[#Sales].[Yr]=(2007) THEN [tempdb].[dbo].[#Sales].[Sales] ELSE NULL END), [Expr1023]=SUM(CASE WHEN [tempdb].[dbo].[#Sales].[Yr]=(2007) THEN [tempdb].[dbo].[#Sales].[Sales] ELSE NULL END)))
|--Sort(ORDER BY:([tempdb].[dbo].[#Sales].[EmpId] ASC))
|--Table Scan(OBJECT:([tempdb].[dbo].[#Sales]))
Run Code Online (Sandbox Code Playgroud)
|--Compute Scalar(DEFINE:([Expr1003]=CASE WHEN [Expr1021]=(0) THEN NULL ELSE [Expr1022] END, [Expr1004]=CASE WHEN [Expr1023]=(0) THEN NULL ELSE [Expr1024] END, [Expr1005]=CASE WHEN [Expr1025]=(0) THEN NULL ELSE [Expr1026] END))
|--Stream Aggregate(GROUP BY:([tempdb].[dbo].[#sales].[EmpId]) DEFINE:([Expr1021]=COUNT_BIG([Expr1006]), [Expr1022]=SUM([Expr1006]), [Expr1023]=COUNT_BIG([Expr1007]), [Expr1024]=SUM([Expr1007]), [Expr1025]=COUNT_BIG([Expr1008]), [Expr1026]=SUM([Expr1008])))
|--Compute Scalar(DEFINE:([Expr1006]=CASE WHEN [tempdb].[dbo].[#sales].[Yr]=(2005) THEN [tempdb].[dbo].[#sales].[Sales] ELSE NULL END, [Expr1007]=CASE WHEN [tempdb].[dbo].[#sales].[Yr]=(2006) THEN [tempdb].[dbo].[#sales].[Sales] ELSE NULL END, [Expr1008]=CASE WHEN [tempdb].[dbo].[#sales].[Yr]=(2007) THEN [tempdb].[dbo].[#sales].[Sales] ELSE NULL END))
|--Sort(ORDER BY:([tempdb].[dbo].[#sales].[EmpId] ASC))
|--Table Scan(OBJECT:([tempdb].[dbo].[#sales]))
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
8628 次 |
最近记录: |