0

我在 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# 或任何其他面向对象的语言中执行此操作,这会向我尖叫递归。我认为递归 CTE 可能更有效。我只成功地为 Heirarchy 的以下类型的查询成功地完成了任何递归 CTE,并且我无法成功地围绕一个可以以另一种方式实现这一目标的查询。

任何帮助或简单的方式将不胜感激。

4

3 回答 3

1

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

事实证明,在 SQL Server 2005 及更高版本中计算运行总计有四种基本方法:通过连接、子查询、递归 CTE 和游标。我遇到了Jerry Nixon 的一篇博客文章,其中展示了前三个。结果相当惊人。与连接和子查询解决方案相比,递归 CTE 几乎快得令人难以置信。

不幸的是,他没有包含游标解决方案。我创建了一个并使用他的示例数据在我的计算机上运行它。游标解决方案仅比递归 CTE 慢一点 - 413ms vs. 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);
于 2012-07-14T19:54:22.510 回答
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 个项目,仅在方差 >= 一分钱的情况下发生一次。

感谢所有在这里回答的人。我最终走了一条不同的路,但没有你我不会找到它。

于 2012-07-18T20:58:36.507 回答
0

这是您可以尝试的一件事。诚然,这不是我必须处理现实世界的事情,但我远离光标。我拿了你的临时表@temp_calculate并添加了一个IDorder by 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。直到你去那里,这是处理滑动聚合的一种方法。

于 2012-07-13T22:45:27.040 回答