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 被正确索引(至少 ClientIdCustomerIdOriginSalesDate),您的查询应该执行良好甚至更快,因为使用 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(...),但由于您已经使用它们创建了一个存储过程,因此它们将使查询更具可读性。