【问题标题】:How to Optimize this SQL Script如何优化此 SQL 脚本
【发布时间】:2023-03-17 23:03:01
【问题描述】:

我使用 SQL Server 2008 。 我尝试制作报告,需要计算我表中每一行的新列。 我写了这个函数:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      <Author,,Name>
-- Create date: <Create Date, ,>
-- Description: <Description, ,>
-- =============================================
ALTER FUNCTION [dbo].[fn_CalcProductStock]
    (
      @ProductID INT = NULL ,
      @ToDate DATETIME = NULL ,
      @FinYear INT = NULL ,
      @InventoryID INT = NULL
    )
RETURNS FLOAT
AS
    BEGIN

        DECLARE @stock FLOAT
        SET @stock = ISNULL(( SELECT    SUM(IDI.InvDocItemNumbers)
                              FROM      InvDocItem IDI
                                        LEFT OUTER         JOIN InvDoc ID ON IDI.CenterID = ID.CenterID
                                                              AND IDI.InvDocID = ID.InvDocID
                              WHERE     IDI.ProductID = @ProductID
                                        AND ID.FinYearID = @FinYear
                                        AND ( ID.InventoryID = @InventoryID
                                              OR @InventoryID IS NULL
                                            )
                                        AND ID.InvDocDate < @ToDate
                                        AND ( ID.InvTransTypeID = 1  
                                              OR ID.InvTransTypeID = 4  
                                              OR ID.InvTransTypeID = 6 
                                              OR ID.InvTransTypeID = 13 
                                              OR ID.InvTransTypeID = 9  
                                            )
                            ), 0)
            - ISNULL(( SELECT   SUM(II.InvoiceItemNumbers)
                       FROM     InvoiceItem II
                                LEFT OUTER    JOIN Invoice I ON ( II.CenterID = I.CenterID
                                                              AND II.InvoiceID = I.InvoiceID
                                                              )
                       WHERE    II.ProductID = @ProductID
                                AND I.FinYearId = @FinYear
                                AND I.InvoiceDate < @ToDate
                                AND ( I.InventoryID = @InventoryID
                                      OR @InventoryID IS NULL
                                    )   --Tehran:1  Tehrantakh:101  Mashhad:21  Tabriz:6
                                AND ( I.InvoiceType = 1      
                                      OR I.InvoiceType = 3   
                                    )
                     ), 0)
            + ISNULL(( SELECT   SUM(II.InvoiceItemNumbers)
                       FROM     InvoiceItem II
                                LEFT OUTER     JOIN Invoice I ON II.CenterID = I.CenterID
                                                              AND II.InvoiceID = I.InvoiceID
                       WHERE    II.ProductID = @ProductID
                                AND I.FinYearId = @FinYear
                                AND I.InvoiceDate < @ToDate
                                AND ( I.InventoryID = @InventoryID
                                      OR @InventoryID IS NULL
                                    )
                                AND ( I.InvoiceType = 2   
                                      OR I.InvoiceType = 4   
                                    )
                     ), 0)
            - ISNULL(( SELECT   SUM(IDI.InvDocItemNumbers)
                       FROM     InvDocItem IDI
                                LEFT OUTER JOIN InvDoc ID ON IDI.CenterID = ID.CenterID
                                                             AND IDI.InvDocID = ID.InvDocID
                       WHERE    IDI.ProductID = @ProductID
                                AND ID.InvTransTypeID = 3 
                                AND ID.FinYearID = @FinYear
                                AND ( ID.InventoryID = @InventoryID
                                      OR @InventoryID IS NULL
                                    )
                                AND ID.InvDocDate < @ToDate
                     ), 0)

    -- Return the result of the function
        RETURN @stock

    END

在这种情况下,我也尝试使用此功能:

SELECT  Prdct.InventoryID ,
        Prdct.ProductID ,
        ( dbo.fn_CalcProductStock(Prdct.ProductID, '2014-04-29', 92, 101) )
FROM    ProductInv AS Prdct

这有真实的计算结果,但需要很长时间 就像40分钟......!!!适用于 3000 种产品

我怎样才能使它的性能更好?

【问题讨论】:

  • 除了任何性能问题之外,您的函数可能没有按您想要的方式运行。唯一可以安全忽略的变量是InventoryIdFinYearToDate 将需要一个值来返回 any 结果。 ProductId 条件的定位意味着连接实际上是INNER JOINs - 如果您不提供值,您也不会得到任何结果(因为null 不等于 @ 987654329@)。您的查询似乎获得了 quantity 的项目,但对我而言,ItemNumber 本质上是一个 key - 这只是一个命名模糊的变量,还是一个计数?
  • 它是发票或文档中每个产品的计数..

标签: sql sql-server performance sql-server-2008 tsql


【解决方案1】:

不幸的是,具有数据访问权限的标量 UDF 是臭名昭著的性能杀手。它们以 RBAR 方式执行,不与主查询一起优化。

没有简单的方法可以修复它。我通常尝试做的是将主查询的结果存储到临时表中,然后考虑如何从函数中重写查询,以便我可以对所有行执行一次。或者,由于您实际上有四个选择,因此分别运行它们中的每一个,将结果存储到临时表中,然后将它们全部连接在一起。

重点是不要对每一行执行一次,这样它有更好的机会被正确优化。

【讨论】:

    【解决方案2】:

    我认为这会奏效

    CREATE FUNCTION [dbo].[usp_CalcProductStock] (
        -- Add the parameters for the function here
        @ProductID INT = NULL
        ,@ToDate DATETIME = NULL
        ,@FinYear INT = NULL
        ,@InventoryID INT = NULL
        )
    RETURNS FLOAT
    AS
    BEGIN
        DECLARE @stock FLOAT
        DECLARE @DocItems TABLE (
            InvTransTypeID INT
            ,SumOfInvDocItemNumbers BIGINT
            )
    
        INSERT INTO @DocItems
        SELECT ID.InvTransTypeID
            ,SUM(IDI.InvDocItemNumbers) AS SumOfInvDocItemNumbers
        FROM (
            SELECT InvDocItemNumbers
                ,CenterID
                ,InvDocID
            FROM InvDocItem
            WHERE ProductID = @ProductID
            ) IDI
        LEFT JOIN (
            SELECT CenterID
                ,InvDocID
                ,InvTransTypeID
            FROM InvDoc
            WHERE ID.FinYearID = @FinYear
                AND (
                    ID.InventoryID = @InventoryID
                    OR @InventoryID IS NULL
                    )
                AND ID.InvDocDate < @ToDate
            ) ID ON IDI.CenterID = ID.CenterID
            AND IDI.InvDocID = ID.InvDocID HERE
        GROUP BY ID.InvTransTypeID
    
        DECLARE @InvoiceItems TABLE (
            InvoiceType NVARCHAR(250)
            ,SumofInvoiceItemNumbers BIGINT
            )
    
        INSERT INTO @InvoiceItems
        SELECT I.InvoiceType
            ,SUM(II.InvoiceItemNumbers) AS SumofInvoiceItemNumbers
        FROM (
            SELECT CenterID
                ,InvoiceID
                ,InvoiceItemNumbers
            FROM InvoiceItem
            WHERE ProductID = @ProductID
            ) II
        LEFT JOIN (
            SELECT CenterID
                ,InvoiceID
                ,InvoiceType
            FROM Invoice
            WHERE I.FinYearId = @FinYear
                AND I.InvoiceDate < @ToDate
                AND (
                    I.InventoryID = @InventoryID
                    OR @InventoryID IS NULL
                    )
            ) I ON II.CenterID = I.CenterID
            AND II.InvoiceID = I.InvoiceID
        GROUP BY I.InvoiceType
    
        DECLARE @DocItems_I FLOAT;
        DECLARE @DocItems_II FLOAT;
        DECLARE @InvoiceItems_I FLOAT;
        DECLARE @InvoiceItems_II FLOAT;
    
        SELECT @DocItems_I = ISNULL(SUM(SumOfInvDocItemNumbers), 0)
        FROM @DocItems
        WHERE InvTransTypeID IN (
                1
                ,4
                ,6
                ,9
                ,13
                )
    
        SELECT @InvoiceItems_I = ISNULL(SUM(SumofInvoiceItemNumbers), 0)
        FROM @InvoiceItems
        WHERE InvoiceType IN (
                1
                ,3
                )
    
        SELECT @InvoiceItems_II = ISNULL(SUM(SumofInvoiceItemNumbers), 0)
        FROM @InvoiceItems
        WHERE InvoiceType IN (
                2
                ,4
                )
    
        SELECT @DocItems_II = ISNULL(SUM(SumOfInvDocItemNumbers), 0)
        FROM @DocItems
        WHERE InvTransTypeID IN (3)
    
        SET @stock = @DocItems_I - @InvoiceItems_I + @InvoiceItems_II - @DocItems_II
    
        RETURN @stock
    END
    GO
    

    【讨论】:

    • 这里的区别只是我使用了变量表,变量表类似于临时表。虽然变量表对临时表有一些限制,但这并不意味着我们不应该使用它。这取决于您的要求。
    【解决方案3】:

    您可能想改用table-valued function

    CREATE FUNCTION Calculated_Product_Stock (@toDate DATETIME,
                                              @finYear INT,
                                              @inventoryId INT = NULL)
    
    RETURNS table AS
    RETURN (SELECT COALESCE(Doc.productId, Inv.productId) AS productId,
                   COALESCE(Doc.stock, 0) + COALESCE(Inv.stock, 0) AS stock
            FROM (SELECT Item.productId,
                         SUM(CASE WHEN Doc.invTransTypeId = 3 
                                       THEN 0 - Item.invDocItemNumbers
                                       ELSE Item.invDocItemNumbers END) AS stock
                  FROM InvDoc Doc
                  JOIN InvDocItem Item
                    ON Item.centerId = Doc.centerId
                       AND Item.invDocId = Doc.invDocId
                  WHERE (@inventoryId IS NULL OR Doc.inventoryId = @inventoryId)
                        AND Doc.finYearId = @finYear
                        AND Doc.invDocDate < @toDate
                        AND Doc.invTransTypeId IN (1, 3, 4, 6, 9, 13)
                  GROUP BY Item.productId) Doc
    
            FULL JOIN (SELECT Item.productId,
                              SUM(CASE WHEN Inv.invoiceType IN (1, 3) 
                                            THEN 0 - Item.invoiceItemNumbers
                                            ELSE Item.invoiceItemNumbers END) AS stock
                       FROM Invoice Inv
                       JOIN InvoiceItem Item
                         ON Item.invoiceId = Inv.invoiceId
                            AND Item.centerId = Inv.centerId
                       WHERE (@inventoryId IS NULL OR Inv.inventoryId = @inventoryId)
                             AND Inv.finYearId = @finYear
                             AND Inv.invoiceDate < @toDate
                             AND Inv.invoiceType IN (1, 2, 3, 4)
                       GROUP BY Item.productId) Inv
                   ON Inv.productId = Doc.productId);
    

    然后可以将函数包含在查询中,如下所示:

    SELECT Product.inventoryId, Product.productId, COALESCE(Invoice.stock, 0)
    FROM ProductInv Product
    LEFT JOIN Calculated_Product_Stock('2014-04-29', 92, 101) Invoice
           ON Invoice.productId = Product.productId
    

    (没有经过测试,因为我没有任何东西可以运行它。而且我从未使用过此功能)
    与往常一样,测试一下它是否适合您的情况。

    【讨论】:

    • 谢谢,但是当我尝试保存函数时,显示此错误:Msg 4104, Level 16, State 1, Procedure Calculated_Product_Stock, Line 30 The multi-part identifier "Doc.inventoryId"无法绑定。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-31
    • 2021-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-20
    相关资源
    最近更新 更多