【问题标题】:Optimize Stored Procedure for calculation of weighted Average优化计算加权平均的存储过程
【发布时间】:2019-07-30 06:58:09
【问题描述】:

我在我的会计应用程序中使用以下代码计算产品的加权平均值,但是当我的记录增加时,它会引发错误“在语句完成之前已用尽最大递归 100”有什么优化建议吗?

   ALTER PROCEDURE [dbo].[ProductBackAveragePrice]
(
    @InvoiceFK int,@FromDate char(10),@ToDate char(10),@FinancialFK tinyint
)
AS
    SET NOCOUNT ON;


declare @InvoiceID int=0 , @ProductFK int=0
DECLARE q1_cursor CURSOR Dynamic for select InvoiceFK,ProductFK from Sales.InvoiceDetail where FinancialPeriodFK=@FinancialFK and InvoiceFK=@InvoiceFK
open q1_cursor 
fetch next from q1_cursor into @InvoiceID,@ProductFK

while @@fetch_status=0
begin

with CTE as (
  select RowID,Date,InvoiceID,InvoiceNumber,InvoiceKindFK,[I/O],
    OrderQty, UnitPrice=cast(UnitPrice-(isnull(DiscountAmount,0)/nullif(OrderQty,0)) as decimal),
   cast( OrderQty as decimal) as QuantityOnHand,
    (cast(UnitPrice-(isnull(DiscountAmount,0)/nullif(OrderQty,0)) as decimal)) as AverageUnitCost
    from [dbo].[ProductInOutProduct] (@ProductFK,@FromDate,@ToDate,@FinancialFK)
    where RowId = 1 -- Starting condition for your single product sample data.
  union all
  select R.RowID, R.Date,R.InvoiceID,R.InvoiceNumber,R.InvoiceKindFK, R.[I/O],
    R.OrderQty, case when (R.InvoiceKindFK=3) then CTE.AverageUnitCost else cast(R.UnitPrice-(isnull(R.DiscountAmount,0)/nullif(R.OrderQty,0)) as decimal) end,

      cast(case
      when R.[I/O] = 2 then CTE.QuantityOnHand - R.OrderQty -- Sales don't affect the average unit cost.
      when R.[I/O] = 1 then CTE.QuantityOnHand + R.OrderQty
      else ( CTE.QuantityOnHand) end
         as decimal),

    -- My accounting is pretty rusty, but this should do some sort of useful averaging.
   cast(case
      when (R.[I/O] = 2 or R.InvoiceKindFK=3) then cast(CTE.AverageUnitCost as decimal) -- Sales don't affect the average unit cost.
      else ( CTE.AverageUnitCost * CTE.QuantityOnHand + (R.UnitPrice-(isnull(R.DiscountAmount,0)/nullif(R.OrderQty,0))) * R.OrderQty ) /
        nullif(( CTE.QuantityOnHand + R.OrderQty ),0) end
         as decimal)

    from CTE inner join
      [dbo].[ProductInOutProduct] (@ProductFK,@FromDate,@ToDate,@FinancialFK) as R on R.RowId = CTE.RowID + 1 -- Row by row.
    )

    UPDATE dt
SET dt.BackPrice = dtu.AverageUnitCost
FROM Sales.InvoiceDetail dt
INNER JOIN CTE dtu ON dt.InvoiceFK = dtu.InvoiceID
 WHERE
        dt.FinancialPeriodFK = @FinancialFK and dt.InvoiceFK=dtu.InvoiceID and ProductFK=@ProductFK
option (maxrecursion 0);    



    fetch next from q1_cursor into @InvoiceID,@ProductFK
end
close q1_cursor
deallocate q1_cursor

【问题讨论】:

  • @PeterSmith 因为我只将 InvoiceID 传递给光标,所以它在光标上运行一次
  • 你的问题究竟是什么?
  • TSQL Maxrecursion on a cte(和许多其他人)的可能重复
  • @Squirrel 运行此 SP 时由于记录巨大而引发错误,所以请建议我优化代码
  • @EdHarper 没明白你的意思?

标签: sql-server tsql recursion stored-procedures weighted-average


【解决方案1】:

您需要在基于 SET 而不是 CURSOR 中重写您的逻辑,并批量更新记录而不是循环访问它们。没有任何测试很难编写它,但它应该看起来像这样:

ALTER PROCEDURE [dbo].[ProductBackAveragePrice]
    (
      @InvoiceFK INT ,
      @FromDate CHAR(10) ,
      @ToDate CHAR(10) ,
      @FinancialFK TINYINT
    )
AS
    SET NOCOUNT ON;



    WITH    CTE
              AS ( SELECT   RowID ,
                            Date ,
                            InvoiceID ,
                            InvoiceNumber ,
                            InvoiceKindFK ,
                            [I/O] ,
                            OrderQty ,
                            UnitPrice = CAST(UnitPrice
                            - ( ISNULL(DiscountAmount, 0) / NULLIF(OrderQty, 0) ) AS DECIMAL) ,
                            CAST(OrderQty AS DECIMAL) AS QuantityOnHand ,
                            ( CAST(UnitPrice - ( ISNULL(DiscountAmount, 0)
                                                 / NULLIF(OrderQty, 0) ) AS DECIMAL) ) AS AverageUnitCost ,
                            id.ProductFK
                   FROM     Sales.InvoiceDetail id
                            CROSS APPLY [dbo].[ProductInOutProduct](id.ProductFK,
                                                              @FromDate,
                                                              @ToDate,
                                                              @FinancialFK) p
                   WHERE    RowId = 1 -- Starting condition for your single product sample data.
                            AND FinancialPeriodFK = @FinancialFK
                            AND InvoiceFK = @InvoiceFK
                   UNION ALL
                   SELECT   R.RowID ,
                            R.Date ,
                            R.InvoiceID ,
                            R.InvoiceNumber ,
                            R.InvoiceKindFK ,
                            R.[I/O] ,
                            R.OrderQty ,
                            CASE WHEN ( R.InvoiceKindFK = 3 )
                                 THEN CTE.AverageUnitCost
                                 ELSE CAST(R.UnitPrice
                                      - ( ISNULL(R.DiscountAmount, 0)
                                          / NULLIF(R.OrderQty, 0) ) AS DECIMAL)
                            END ,
                            CAST(CASE WHEN R.[I/O] = 2
                                      THEN CTE.QuantityOnHand - R.OrderQty -- Sales don't affect the average unit cost.
                                      WHEN R.[I/O] = 1
                                      THEN CTE.QuantityOnHand + R.OrderQty
                                      ELSE ( CTE.QuantityOnHand )
                                 END AS DECIMAL) ,

    -- My accounting is pretty rusty, but this should do some sort of useful averaging.
                            CAST(CASE WHEN ( R.[I/O] = 2
                                             OR R.InvoiceKindFK = 3
                                           )
                                      THEN CAST(CTE.AverageUnitCost AS DECIMAL) -- Sales don't affect the average unit cost.
                                      ELSE ( CTE.AverageUnitCost
                                             * CTE.QuantityOnHand
                                             + ( R.UnitPrice
                                                 - ( ISNULL(R.DiscountAmount,
                                                            0)
                                                     / NULLIF(R.OrderQty, 0) ) )
                                             * R.OrderQty )
                                           / NULLIF(( CTE.QuantityOnHand
                                                      + R.OrderQty ), 0)
                                 END AS DECIMAL) ,
                            ProductFK
                   FROM     CTE
                            INNER JOIN [dbo].[ProductInOutProduct](ProductFK,
                                                              @FromDate,
                                                              @ToDate,
                                                              @FinancialFK) AS R ON R.RowId = CTE.RowID
                                                              + 1 -- Row by row.
                 )
        UPDATE  dt
        SET     dt.BackPrice = dtu.AverageUnitCost
        FROM    Sales.InvoiceDetail dt
                INNER JOIN CTE dtu ON dt.InvoiceFK = dtu.InvoiceID
        WHERE   dt.FinancialPeriodFK = @FinancialFK
                AND dt.InvoiceFK = dtu.InvoiceID
                AND ProductFK = @ProductFK
        OPTION  ( MAXRECURSION 0 );    

【讨论】:

    猜你喜欢
    • 2012-04-15
    • 1970-01-01
    • 1970-01-01
    • 2021-11-24
    • 2011-04-22
    • 1970-01-01
    • 1970-01-01
    • 2014-04-21
    • 2010-10-04
    相关资源
    最近更新 更多