2014-04-30 58 views
0

我使用SQL Server 2008。 我尝试做一个报告,并需要计算我的表的每一行的新列。 我写这个函数:如何优化此SQL脚本

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 

也是我尝试在这种情况下使用此fonction:

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

这有真正的计算结果,但需要很长一段时间 像40分钟...! !对于3000产品

我该如何使其性能更好?

+0

除了任何性能问题,您的功能可能**不**按照您的意愿行事。唯一可以忽略的变量是'InventoryId'。 “FinYear”和“ToDate”将需要返回_any_结果的值。 'ProductId'条件的定位意味着连接实际上是'INNER JOIN's - 如果你不提供一个值,你也不会得到结果(因为'null'_does not equal_' null')。你的查询似乎获得了一个_quantity_的项目,但对我来说'ItemNumber'本质上是一个**键** - 这是一个含糊不清的命名变量,还是它是一个计数? –

+0

它是每个产品在发票或文档中的计数.. – merkousha

回答

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 

(没有任何东西经过测试,因为我没有任何东西可以运行它,而我从未使用过这个功能)
一如既往,测试它是否适合您的情况。

+0

谢谢,但是当我尝试保存功能时,此错误已被显示: 消息4104,级别16,状态1,过程计算_产品_库,行30 无法绑定多部分标识符“Doc.inventoryId”。 – merkousha

+0

@merkousha - 小错字,现在纠正(可能)。 –

+0

再次感谢你,表演是从40分钟调整到-0秒!!! – merkousha

1

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

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

重点不在于为每一行执行一次,这样就有更好的机会正确优化。

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 
+0

这里的区别只在于我使用了变量表和变量表类似于临时表。虽然变量表对temp表有一些限制,但并不意味着我们不应该使用它。这取决于你的要求。 –