如何优化此SQL脚本

时间:2014-04-30 04:10:35

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

我使用的是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种产品

我怎样才能让它的表现更好?

3 个答案:

答案 0 :(得分:1)

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

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

关键是不要为每一行执行一次,这样就有更好的机会进行正确优化。

答案 1 :(得分:1)

我认为这会起作用

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

答案 2 :(得分:0)

您可能想要尝试使用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

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