Temp Table 是最佳解决方案吗?
Is a Temp Table the best solution?
我正在构建一个存储过程,它从几个不同的 table 中提取大量不同的数据,我需要一种方法从销售 table 中提取销售信息,然后执行各种对该销售数据的总结。在下面的示例中,我使用临时 table 完成了此操作,但有人建议可能有更好的方法。有没有更有效的方法来完成我在这里所做的事情?
SELECT * INTO #TempSales FROM [Sales] WHERE ClientId = @ClientId
SELECT
[Customer].[CustomerID],
[Customer].[AccountBalAmountOpen],
[Customer].[AccountAgeAmountDays0],
[Customer].[AccountAgeAmountDays30],
[Customer].[AccountAgeAmountDays60],
[Customer].[AccountAgeAmountDays90],
[Customer].[AccountAgeAmountDaysOver90],
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 385) AS ServiceLifeTimeSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 385 AND MONTH(SaleDate) = MONTH(GETDATE()) AND YEAR(SaleDate) = YEAR(GETDATE())) AS ServiceMonthToDateSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 385 AND YEAR(SaleDate) = YEAR(GETDATE())) AS ServiceYearToDateSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 385 AND YEAR(SaleDate) = (YEAR(GETDATE()) - 1)) AS ServicePreviousYearSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 460) AS PartsLifeTimeSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 460 AND MONTH(SaleDate) = MONTH(GETDATE()) AND YEAR(SaleDate) = YEAR(GETDATE())) AS PartsMonthToDateSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 460 AND YEAR(SaleDate) = YEAR(GETDATE())) AS PartsYearToDateSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 460 AND YEAR(SaleDate) = (YEAR(GETDATE()) - 1)) AS PartsPreviousYearSales,
[Orders].[CustomerId] AS ParentCustomerId,
[Orders].[OrderId],
[Orders].[OrderStatus],
[Orders].[UnitId],
[Orders].[FleetId],
[Orders].[CreatedDate] AS OrderCreatedDate,
[Orders].[OrderType],
[OrderParts].[OrderId] AS ParentOrderId,
[OrderParts].[PartId],
[OrderParts].[PartDescription],
[OrderParts].[QuantityShip],
[OrderParts].[QuantityBackOrder],
[OrderParts].[CreatedDate] AS PartCreatedDate
FROM [Customer]
LEFT JOIN [Orders]
ON [Orders].[CustomerId] = [Customer].[CustomerID]
LEFT JOIN [OrderParts]
ON [OrderParts].[OrderId] = [Orders].[OrderId]
WHERE [Customer].[ClientID] = @ClientId
DROP TABLE #TempSales
虽然临时 tables 可用于存储将在其他查询中重用的复杂查询的中间结果,但我认为您在创建临时 table 如下:
SELECT * INTO #TempSales FROM [Sales] WHERE ClientId = @ClientId
您没有使用任何东西加入 table,也没有进行计算或使用聚合函数,因此如果 table 被正确索引(至少 ClientId
、CustomerId
,Origin
和 SalesDate
),您的查询应该执行良好甚至更快,因为使用 SELECT INTO
创建的 tables 没有任何索引,除非您手动添加它们。
但是您可能正在使用临时 table 来提高速度,因为您遇到这样的情况:
AND YEAR(SaleDate) = YEAR(GETDATE())
AND MONTH(SaleDate) = MONTH(GETDATE()) AND YEAR(SaleDate) = YEAR(GETDATE())
which make the query non-sargable 因为您将筛选列包含在函数中。由于这个原因,查询优化器无法正确使用 SaleDate
上的任何现有索引,并且由于时间 table 的行数较少,因此与 [=21] 相比,对其执行完整扫描所需的时间更少=] table.
您可以使用不对筛选列应用任何函数的条件来修复此问题(尽管您可以对常量值使用函数):
declare @thisYearStart as datetime, @nextYearStart as datetime,
@thisMonthStart as datetime, @nextMonthStart as datetime
set @thisYearStart=DATEFROMPARTS(YEAR(GETDATE()),1,1)
set @nextYearStart=DATEADD(year,1,@thisYearStart)
set @thisMonthStart=DATEFROMPARTS(YEAR(GETDATE()),MONTH(GETDATE()),1)
set @nextMonthStart=DATEADD(month,1,@thisMonthStart)
或者如果您使用的 SQL 版本早于 SQL Server 2012:
set @thisYearStart=CAST(CAST(YEAR(GETDATE()) as char(4))+'0101' AS datetime) -- ISO format
set @nextYearStart=DATEADD(year,1,@thisYearStart)
set @thisMonthStart=DATEADD(month,MONTH(GETDATE())-1,@thisYearStart)
set @nextMonthStart=DATEADD(month,1,@thisMonthStart)
然后只需使用:
AND SaleDate>=@thisYearStart AND SaleDate<@nextYearStart
AND SaleDate>=@thisMonthStart AND SaleDate<@nextMonthStart
请注意,您不需要使用变量,您可以直接在 WHERE
条件下使用 DATEFROMPARTS(...)
,但由于您已经使用它们创建了一个存储过程,因此它们将使查询更具可读性。
我正在构建一个存储过程,它从几个不同的 table 中提取大量不同的数据,我需要一种方法从销售 table 中提取销售信息,然后执行各种对该销售数据的总结。在下面的示例中,我使用临时 table 完成了此操作,但有人建议可能有更好的方法。有没有更有效的方法来完成我在这里所做的事情?
SELECT * INTO #TempSales FROM [Sales] WHERE ClientId = @ClientId
SELECT
[Customer].[CustomerID],
[Customer].[AccountBalAmountOpen],
[Customer].[AccountAgeAmountDays0],
[Customer].[AccountAgeAmountDays30],
[Customer].[AccountAgeAmountDays60],
[Customer].[AccountAgeAmountDays90],
[Customer].[AccountAgeAmountDaysOver90],
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 385) AS ServiceLifeTimeSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 385 AND MONTH(SaleDate) = MONTH(GETDATE()) AND YEAR(SaleDate) = YEAR(GETDATE())) AS ServiceMonthToDateSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 385 AND YEAR(SaleDate) = YEAR(GETDATE())) AS ServiceYearToDateSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 385 AND YEAR(SaleDate) = (YEAR(GETDATE()) - 1)) AS ServicePreviousYearSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 460) AS PartsLifeTimeSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 460 AND MONTH(SaleDate) = MONTH(GETDATE()) AND YEAR(SaleDate) = YEAR(GETDATE())) AS PartsMonthToDateSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 460 AND YEAR(SaleDate) = YEAR(GETDATE())) AS PartsYearToDateSales,
(SELECT SUM(SalesAmount) FROM #TempSales WHERE CustomerId = [Customer].[CustomerID] AND Origin = 460 AND YEAR(SaleDate) = (YEAR(GETDATE()) - 1)) AS PartsPreviousYearSales,
[Orders].[CustomerId] AS ParentCustomerId,
[Orders].[OrderId],
[Orders].[OrderStatus],
[Orders].[UnitId],
[Orders].[FleetId],
[Orders].[CreatedDate] AS OrderCreatedDate,
[Orders].[OrderType],
[OrderParts].[OrderId] AS ParentOrderId,
[OrderParts].[PartId],
[OrderParts].[PartDescription],
[OrderParts].[QuantityShip],
[OrderParts].[QuantityBackOrder],
[OrderParts].[CreatedDate] AS PartCreatedDate
FROM [Customer]
LEFT JOIN [Orders]
ON [Orders].[CustomerId] = [Customer].[CustomerID]
LEFT JOIN [OrderParts]
ON [OrderParts].[OrderId] = [Orders].[OrderId]
WHERE [Customer].[ClientID] = @ClientId
DROP TABLE #TempSales
虽然临时 tables 可用于存储将在其他查询中重用的复杂查询的中间结果,但我认为您在创建临时 table 如下:
SELECT * INTO #TempSales FROM [Sales] WHERE ClientId = @ClientId
您没有使用任何东西加入 table,也没有进行计算或使用聚合函数,因此如果 table 被正确索引(至少 ClientId
、CustomerId
,Origin
和 SalesDate
),您的查询应该执行良好甚至更快,因为使用 SELECT INTO
创建的 tables 没有任何索引,除非您手动添加它们。
但是您可能正在使用临时 table 来提高速度,因为您遇到这样的情况:
AND YEAR(SaleDate) = YEAR(GETDATE())
AND MONTH(SaleDate) = MONTH(GETDATE()) AND YEAR(SaleDate) = YEAR(GETDATE())
which make the query non-sargable 因为您将筛选列包含在函数中。由于这个原因,查询优化器无法正确使用 SaleDate
上的任何现有索引,并且由于时间 table 的行数较少,因此与 [=21] 相比,对其执行完整扫描所需的时间更少=] table.
您可以使用不对筛选列应用任何函数的条件来修复此问题(尽管您可以对常量值使用函数):
declare @thisYearStart as datetime, @nextYearStart as datetime,
@thisMonthStart as datetime, @nextMonthStart as datetime
set @thisYearStart=DATEFROMPARTS(YEAR(GETDATE()),1,1)
set @nextYearStart=DATEADD(year,1,@thisYearStart)
set @thisMonthStart=DATEFROMPARTS(YEAR(GETDATE()),MONTH(GETDATE()),1)
set @nextMonthStart=DATEADD(month,1,@thisMonthStart)
或者如果您使用的 SQL 版本早于 SQL Server 2012:
set @thisYearStart=CAST(CAST(YEAR(GETDATE()) as char(4))+'0101' AS datetime) -- ISO format
set @nextYearStart=DATEADD(year,1,@thisYearStart)
set @thisMonthStart=DATEADD(month,MONTH(GETDATE())-1,@thisYearStart)
set @nextMonthStart=DATEADD(month,1,@thisMonthStart)
然后只需使用:
AND SaleDate>=@thisYearStart AND SaleDate<@nextYearStart
AND SaleDate>=@thisMonthStart AND SaleDate<@nextMonthStart
请注意,您不需要使用变量,您可以直接在 WHERE
条件下使用 DATEFROMPARTS(...)
,但由于您已经使用它们创建了一个存储过程,因此它们将使查询更具可读性。