我在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跟踪类型的查询,而且我无法成功地绕过一个可以实现另一种方式的查询。
任何帮助或简单说明如何必须得到赞赏。
答案 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个项目,只发生过一次,其中的变化是> =一分钱。
谢谢大家在这里回答。我最终采取了不同的方式,但没有你我就找不到它。