SQL中的库存价格计算

时间:2012-07-13 18:18:04

标签: sql sql-server-2005 cursor recursive-query

我在SQL 2005中,我正在尝试将此Cursor转换为不是Cursor的东西,以确定这是否是最有效的方法。

        --Create cursor to determint total cost
DECLARE CostCursor CURSOR FAST_FORWARD
        FOR SELECT  ReceiptQty
                   ,Price
            FROM    @temp_calculate
            ORDER BY UpdateDate DESC
OPEN CostCursor 
FETCH Next FROM CostCursor INTO @ReceiptQty,@Price
WHILE @@FETCH_STATUS = 0
      BEGIN
            IF @OnHandQty >= @ReceiptQty
               BEGIN
                           --SELECT @ReceiptQty,@Price, 1,@OnHandQty
                     SET @Cost = @ReceiptQty * @Price
                     SET @OnHandQty = @OnHandQty - @ReceiptQty
                     SET @TotalCost = @TotalCost + @Cost
               END
            ELSE
               BEGIN
                     IF @OnHandQty < @ReceiptQty
                        BEGIN
                              --SELECT @ReceiptQty,@Price, 2,@OnHandQty
                              SET @Cost = @OnHandQty * @Price
                              SET @OnHandQty = 0
                              SET @TotalCost = @TotalCost + @Cost
                              BREAK;
                        END
               END
            FETCH Next FROM CostCursor INTO @ReceiptQty,@Price
      END
CLOSE CostCursor
DEALLOCATE CostCursor

系统需要通过并使用最新收到的库存和价格来确定现有付款是什么。

Ex. 1st Iteration: @OnHandQty = 8 RecievedQty = 5 Price = 1 UpdateDate = 1/20 Results:  @HandQty = 3 @TotalCost = $5
2nd Iteration: @OnHandQty = 3 RecievedQty = 6 Price = 2 UpdateDate = 1/10 Results:  @HandQty = 0 @TotalCost = $11

最终结果告诉我手头的库存我支付了11美元。如果我在C#或任何其他面向对象的语言中这样做,这会尖叫Recursion给我。我认为递归CTE可能更有效率。我只是成功地为Heirarchy做了任何Recursive CTE跟踪类型的查询,而且我无法成功地绕过一个可以实现另一种方式的查询。

任何帮助或简单说明如何必须得到赞赏。

3 个答案:

答案 0 :(得分:1)

这是一个递归的CTE解决方案。必须存在行号列才能使其正常工作。所以我派生了一个包含行号列的新临时表(@ temp_calculate2)。理想情况下,行号列将出现在@temp_calculate中,但我不了解您是否可以修改@temp_calculate的结构。

事实证明,在SQL Server 2005及更高版本中计算运行总计有四种基本方法:通过连接,子查询,递归CTE和游标。我遇到了blog entry by Jerry Nixon,展示了前三个。结果非常惊人。与连接和子查询解决方案相比,递归CTE的速度几乎令人难以置信。

不幸的是,他没有包含游标解决方案。我创建了一个并使用他的示例数据在我的计算机上运行它。光标解决方案只比递归CTE慢一点 - 413ms对273ms。

我不知道游标解决方案与递归CTE相比使用了多少内存。我不熟悉SQL Profiler来获取这些数据,但我很想知道两种方法在内存使用方面的比较。

SET NOCOUNT OFF;

DECLARE @temp_calculate TABLE
(
  ReceiptQty INT,
  Price FLOAT,
  UpdateDate DATETIME
);
INSERT INTO @temp_calculate (ReceiptQty, Price, UpdateDate) VALUES (5, 1.0, '2012-1-20');
INSERT INTO @temp_calculate (ReceiptQty, Price, UpdateDate) VALUES (6, 2.0, '2012-1-10');
INSERT INTO @temp_calculate (ReceiptQty, Price, UpdateDate) VALUES (4, 3.0, '2012-1-08');

DECLARE @temp_calculate2 TABLE
(
  RowNumber INT PRIMARY KEY,
  ReceiptQty INT,
  Price FLOAT
);
INSERT INTO @temp_calculate2
  SELECT
      RowNumber = ROW_NUMBER() OVER(ORDER BY UpdateDate DESC),
      ReceiptQty,
      Price
    FROM
      @temp_calculate;

;WITH LineItemCosts (RowNumber, ReceiptQty, Price, RemainingQty, LineItemCost)
AS
(
  SELECT
      RowNumber,
      ReceiptQty,
      Price,
      8, -- OnHandQty
      ReceiptQty * Price
    FROM
      @temp_calculate2
    WHERE
      RowNumber = 1

  UNION ALL

  SELECT
      T2.RowNumber,
      T2.ReceiptQty,
      T2.Price,
      LIC.RemainingQty - LIC.ReceiptQty,
      (LIC.RemainingQty - LIC.ReceiptQty) * T2.Price
    FROM
      LineItemCosts AS LIC
      INNER JOIN @temp_calculate2 AS T2 ON LIC.RowNumber + 1 = T2.RowNumber
)
/* Swap these SELECT statements to get a view of
   all of the data generated by the CTE. */
--SELECT * FROM LineItemCosts;
SELECT
    TotalCost = SUM(LineItemCost)
  FROM
    LineItemCosts
  WHERE
    LineItemCost > 0
  OPTION
    (MAXRECURSION 10000);

答案 1 :(得分:0)

这是你可以尝试的一件事。不可否认,这不是我必须处理现实世界的事情,但我远离游标。我拿了你的临时表@temp_calculate并添加了一个由ID排序的UPDATEDATE。您还可以将输出中想要的字段添加到临时表 - @HandQty和@TotalCost以及名为IndividulaCost的新表中 - 并运行此查询并将其用于UPDATE @HandQty和IndividulaCost。之后再运行一次UPDATE,采用此处使用的相同概念来获取和更新总费用。 (实际上,您可以在插入临时表时使用其中的一部分并消除步骤。)

我认为它不是很好,但我相信它比光标更好。玩它,看看你的想法。

DECLARE @OnHandQty int
set @OnHandQty = 8
SELECT a.ID,
RECEIPTQty + TOTALOFFSET AS CURRENTOFFSET,
TOTALOFFSET,
CASE WHEN @OnHandQty - (RECEIPTQty + TOTALOFFSET) > 0 THEN RECEIPTQTY * PRICE
     ELSE (@OnHandQty  - TOTALOFFSET) * Price END AS CALCPRICE,
CASE WHEN @OnHandQty - RECEIPTQTY - TOTALOFFSET > 0 THEN @OnHandQty - RECEIPTQTY -   TOTALOFFSET
     ELSE 0 END AS HandQuantity
FROM SO_temp_calculate a
CROSS APPLY (   SELECT ISNULL(SUM(ReceiptQty), 0) AS TOTALOFFSET
                     FROM SO_temp_calculate B where a.id > b.id
                  ) X

返回值:

ID  CURRENTOFFSET   TOTALOFFSET CALCPRICE   HandQuantity
----------------------------------------------------------------
1          5           0           5           3
2         11           5           6           0

如果您使用的是SQL SERVER 2012,则可以将RANK个函数与OVER子句和ROWS UNBOUNDED PRECEDING一起使用。在你去那里之前,这是处理滑动聚合的一种方法。

答案 2 :(得分:0)

CREATE CLUSTERED INDEX IDX_C_RawData_ProductID_UpdateDate ON #RawData (ProductID ASC , UpdateDate DESC , RowNumber ASC)

    DECLARE @TotalCost Decimal(30,5)
    DECLARE @OnHandQty Decimal(18,5)
    DECLARE @PreviousProductID Int

    UPDATE  #RawData
    SET     @TotalCost = TotalCost = CASE
                                          WHEN RowNumber > 1
                                          AND @OnHandQty >= ReceiptQuantity THEN @TotalCost + (ReceiptQuantity * Price)
                                          WHEN RowNumber > 1
                                          AND @OnHandQty < ReceiptQuantity THEN @TotalCost + (@OnHandQty * Price)
                                          WHEN RowNumber = 1
                                          AND OnHand >= ReceiptQuantity THEN (ReceiptQuantity * Price)
                                          WHEN RowNumber = 1
                                          AND OnHand < ReceiptQuantity THEN (OnHand * Price)
                                     END
           ,@OnHandQty = OnHandQty = CASE
                                          WHEN RowNumber > 1
                                          AND @OnHandQty >= ReceiptQuantity THEN @OnHandQty - ReceiptQuantity
                                          WHEN RowNumber > 1
                                          AND @OnHandQty < ReceiptQuantity THEN 0
                                          WHEN RowNumber = 1
                                          AND OnHand >= ReceiptQuantity THEN (OnHand - ReceiptQuantity)
                                          WHEN RowNumber = 1
                                          AND OnHand < ReceiptQuantity THEN 0
                                     END/*,
            @PreviousProductID = ProductID*/
    FROM    #RawData WITH (TABLOCKX)
    OPTION (MAXDOP 1)

Welp,这是我最终提出的解决方案。我喜欢认为那些看着#sqlhelp主题标签的优秀人士指的是Jeff Moden的这篇文章:

http://www.sqlservercentral.com/articles/T-SQL/68467/

我最终不得不在桌子上使用Rownumber,因为它没有正确地获得第一组案例。使用这个构造我带回了数据集从17分钟,最好的我能够做到,在我速度极慢的开发盒上12秒。我相信生产会降低更多。

我测试了输出,我得到了与旧方法完全相同的结果,除了当同一产品的2个项目具有不同价格且更新时间完全相同时。一种方式可以选择与另一种不同的顺序。它有15,624个项目,只发生过一次,其中的变化是> =一分钱。

谢谢大家在这里回答。我最终采取了不同的方式,但没有你我就找不到它。