用查询替换游标

Replace cursors with queries

假设我有一个 6 小时的预订和 3 个折扣,每个 2 小时。我想将我的预订分成 3 个部分,这样我可以为每个折扣分配 2 小时。

它会 return 像这样:

BookingId 1 | DiscountId 1 | Qty 2
BookingId 1 | DiscountId 2 | Qty 2
BookingId 1 | DiscountId 3 | Qty 2

然后我会将这些记录插入另一个 table。

我正在使用高度优化的查询来确定每个折扣可用的小时数。但是,我无法找到一种 "good" 方法来在不使用光标的情况下将我的预订分配给每个折扣。

(...)

WHILE @@FETCH_STATUS = 0 
BEGIN
    IF @RequiredQty = 0 
       RETURN

    IF @RequiredQty <= @AvailableQty  
    BEGIN
        INSERT INTO discount.Usage (DiscountId, BookingId, Quantity) 
        VALUES (@DiscountId, @BookingId, @RequiredQty)

        SET @RequiredQty = 0
    END

    IF @RequiredQty > @AvailableQty  
    BEGIN
        INSERT INTO discount.Usage (DiscountId, BookingId, Quantity) 
        VALUES (@DiscountId, @BookingId, @AvailableQty)

        SET @RequiredQty -= @AvailableQty
    END

    FETCH NEXT FROM ecursor INTO @DiscountId, @AvailableQty
END

DEALLOCATE ecursor

我尝试构建相应的查询,但无法同时 select 和分配变量。使用游标并不是真正的问题(除了一些潜在的性能问题)但我只是想知道使用最新的 SQL 服务器我们是否可以将旧游标转换为更好的东西?

谢谢, 塞布

您可以使用CTE RECURSIVE制作一个Table。

像这样。

DECLARE @BookingId INT = 1;
DECLARE @RequiredQty INT = 2;
DECLARE @Hours INT = 7;

CREATE TABLE #T
(
    BookingId  INT,
    DiscountId INT,
    Quantity   INT
)

;WITH CTE([Count],[Quantity],Rk) AS  
(
    SELECT
           CASE 
                WHEN [HOURS] - @RequiredQty > @RequiredQty THEN @RequiredQty
                ELSE [HOURS] - @RequiredQty
           END ,
           T.HOURS,1
    FROM
    (
        SELECT @Hours [HOURS]
    ) AS T
    UNION ALL
    SELECT CASE  
                WHEN CTE.[Quantity] - @RequiredQty > @RequiredQty THEN @RequiredQty
                ELSE CTE.[Quantity] - @RequiredQty
           END AS [Count],
           CTE.[Quantity] - @RequiredQty, 
           RK + 1
    FROM CTE
    WHERE CTE.[Quantity] - @RequiredQty > 0
)

INSERT INTO #T(BookingId,DiscountId,Quantity)
SELECT @BookingId,Rk,[Count] FROM CTE
option (maxrecursion 0)

select * from #T

SQLDEMO

这是另一种方法,但不知道这段代码是否比游标有更好的性能。

DECLARE @DiscountStocks TABLE (Id INT IDENTITY(1,1), DiscountId INT, LastQty INT)
INSERT INTO @DiscountStocks (DiscountId, LastQty) VALUES (1, 5)
INSERT INTO @DiscountStocks (DiscountId, LastQty) VALUES (2, 2)
INSERT INTO @DiscountStocks (DiscountId, LastQty) VALUES (3, 1)

DECLARE @DiscountBookings TABLE (Id INT IDENTITY(1,1), DiscountId INT, BookingId INT, Qty INT)

DECLARE @BookingDiscount TABLE (Id INT IDENTITY(1,1), BookingId INT, DiscountId INT, Qty INT)
INSERT INTO @BookingDiscount (BookingId, DiscountId, Qty) VALUES (1, 1, 4)
INSERT INTO @BookingDiscount (BookingId, DiscountId, Qty) VALUES (1, 2, 2)
INSERT INTO @BookingDiscount (BookingId, DiscountId, Qty) VALUES (1, 3, 1)
INSERT INTO @BookingDiscount (BookingId, DiscountId, Qty) VALUES (2, 1, 1)
INSERT INTO @BookingDiscount (BookingId, DiscountId, Qty) VALUES (2, 2, 2)

SELECT BD.Id AS BDId, DS.Id AS DSId, DS.LastQty, BD.Qty
, DS.LastQty - (SELECT SUM(Qty) FROM @BookingDiscount WHERE Id <= BD.Id AND DiscountId = BD.DiscountId) AS QtyAfterSubstract
INTO #LastDiscountStock
FROM @DiscountStocks DS
INNER JOIN @BookingDiscount BD ON DS.DiscountId = BD.DiscountId
ORDER BY BD.Id, DS.Id

INSERT INTO @DiscountBookings (DiscountId, BookingId, Qty)
SELECT DSId, BDId, Qty
FROM #LastDiscountStock
WHERE QtyAfterSubstract >= 0

DROP TABLE #LastDiscountStock

SELECT * FROM @DiscountBookings