1

首先,让我马上声明,我很清楚游标通常是邪恶的,不应该使用——我只是想使用集合,但就是想不出针对这个特定问题的基于集合的解决方案. 如果你告诉我去做一些基于集合的操作,那么我完全赞成,如果你能告诉我你将如何编写这个特定的问题。

基本上,我有很多库存物品需要购买。我想根据最便宜的可用价格进行采购,我知道供应商的价格和他们的库存水平。这里还有一个包装尺寸的问题,如果可能的话,我想按包装尺寸购买。

我已经将我需要购买的东西的清单#needorders以及供应商的库存水平和价格拉入了#orderedprices。下面我遍历游标CUR_NEEDED并创建辅助游标CUR_AVAILABLE

    DECLARE CUR_NEEDED CURSOR LOCAL SCROLL_LOCKS
FOR
SELECT        GoodID
        , ConditionID
        , QuantityToShip
        , OrderStatusID
        , RetailerID
        , PackSize
FROM        #needorders
ORDER BY      GoodID
        , ConditionID
        , PurchaseDate DESC
FOR UPDATE

OPEN CUR_NEEDED
FETCH NEXT FROM CUR_NEEDED INTO @GoodID, @ConditionID, @QuantityToShip, @OrderStatusID, @RetailerID, @PackSize

WHILE @@FETCH_STATUS = 0
    BEGIN
        DECLARE CUR_AVAILABLE CURSOR LOCAL SCROLL_LOCKS
        FOR
        SELECT        SupplierStocklistItemID
                , SupplierID
                , StockLevel
                , SupplierCurrencyID
                , CostPrice
        FROM        #orderedprices
        WHERE       #orderedprices.GoodID = @GoodID
        AND     #orderedprices.ConditionID = @ConditionID
        AND     #orderedprices.StockLevel > 0
        ORDER BY    #orderedprices.PriceRank
        FOR UPDATE

        OPEN CUR_AVAILABLE
        FETCH NEXT FROM CUR_AVAILABLE INTO @SupplierStocklistItemID, @SupplierID, @StockLevel, @SupplierCurrencyID, @CostPrice

        WHILE @@FETCH_STATUS = 0
            BEGIN
                /*
                Buy as many @PackSize as we need to cover how many we require, unless the supplier
                only has a certain number, in which case buy that number.
                E.g., need 14, pack size 5, 2 suppliers
                Supplier A has 11
                Supplier B has 40
                Buy 9 from Supplier A, with our remaining need being 3.
                Buy 5 from supplier B, with our remaining need being -2
                */
                --feed rows into #supplierpurchasesbase while @StockLevel > 0

                --Figure out how many we need to buy, based upon PackSize
                IF @QuantityToShip % @PackSize > 0
                    BEGIN
                        SET @Buy = @QuantityToShip - @QuantityToShip % @PackSize + @PackSize
                    END
                ELSE
                    BEGIN
                        SET @Buy = @QuantityToShip
                    END

                IF @StockLevel < @Buy
                    BEGIN
                        --PRINT 'Supplier only has ' + CAST(@StockLevel AS VARCHAR) + ' for us to buy.'
                        SET @Buy = @StockLevel
                    END

                INSERT INTO #supplierpurchasesbase (
                      GoodID
                    , ConditionID
                    , SupplierStocklistItemID
                    , Quantity
                    , SupplierID
                    , SupplierCurrencyID
                    , CostPrice
                    , RetailerID )
                SELECT    @GoodID
                    , @ConditionID
                    , @SupplierStocklistItemID
                    , @Buy
                    , @SupplierID
                    , @SupplierCurrencyID
                    , @CostPrice
                    , @RetailerID

                --update @QuantityToShip & the row in CUR_AVAILABLE
                IF @StockLevel <= @Buy
                    BEGIN
                        UPDATE  CUR_AVAILABLE
                        SET StockLevel = @StockLevel - @Buy
                        WHERE   CURRENT OF CUR_AVAILABLE

                        SET @QuantityToShip = 0
                    END
                ELSE
                    BEGIN
                        UPDATE  CUR_AVAILABLE
                        SET StockLevel = 0
                        WHERE   CURRENT OF CUR_AVAILABLE

                        SET @QuantityToShip = @QuantityToShip - @Buy
                    END

                --update the stocklevel so we don't see the thing again if we've used it up.

                IF @QuantityToShip = 0  --Don't need any more
                    BEGIN
                        UPDATE  CUR_NEEDED
                        SET OrderStatusID = @StatusPendingPO
                        WHERE   CURRENT OF CUR_NEEDED

                        BREAK
                    END
                ELSE    --Need more, move next, if we can
                    FETCH NEXT FROM CUR_AVAILABLE INTO @SupplierStocklistItemID, @SupplierID, @StockLevel, @SupplierCurrencyID, @CostPrice
            END
        CLOSE       CUR_AVAILABLE
        DEALLOCATE  CUR_AVAILABLE

        FETCH NEXT FROM CUR_NEEDED INTO @GoodID, @ConditionID, @QuantityToShip, @OrderStatusID, @RetailerID, @PackSize
    END
CLOSE       CUR_NEEDED
DEALLOCATE  CUR_NEEDED

我遇到的问题是我得到了错误

无效的对象名称“CUR_AVAILABLE”。

当我尝试更新时CURRENT OF CUR_AVAILABLE

我尝试将CUR_AVAILABLE光标定义为@CUR_AVAILABLE但得到不同的错误。我尝试在 的循环CUR_AVAILABLE之外定义光标,我尝试不关闭/取消分配光标等。这些似乎都不起作用。WHILECUR_NEEDED

在这里我会出错的任何想法(除了不使用集合,除非您有基于集合的解决方案)?

4

2 回答 2

2

以下查询使用递归 CTE,因此不能被视为真正基于集合的解决方案。尽管如此,我仍然希望它比您的两个游标表现更好(或者至少值得尝试):

WITH buys (
  GoodID,
  ConditionID,
  SupplierStocklistItemID,
  Quantity,
  SupplierID,
  SupplierCurrencyID,
  CostPrice,
  RetailerID,
  PriceRank,
  RemainingNeed,
  PackSize
)
AS (
  SELECT
    GoodID,
    ConditionID,
    SupplierStocklistItemID = 0,
    Quantity                = 0,
    SupplierID              = 0,
    SupplierCurrencyID      = 0,
    CostPrice               = CAST(0.00 AS decimal(10,2)),
    RetailerID,
    PriceRank               = 0,
    RemainingNeed           = QuantityToShip,
    PackSize
  FROM #needorders
  UNION ALL
  SELECT
    p.GoodID,
    p.ConditionID,
    p.SupplierStockListItemID,
    Quantity = y.CurrentBuy,
    p.SupplierID,
    p.SupplierCurrencyID,
    p.CostPrice,
    b.RetailerID,
    p.PriceRank,
    RemainingNeed = b.RemainingNeed - y.CurrentBuy,
    b.PackSize
  FROM #orderedprices p
  INNER JOIN buys b ON p.GoodID = b.GoodID
    AND p.ConditionID = b.ConditionID
    AND p.PriceRank = b.PriceRank + 1
  CROSS APPLY (
    SELECT RemainingNeedAdjusted =
      (b.RemainingNeed + b.PackSize - 1) / b.PackSize * b.PackSize
  ) x
  CROSS APPLY (
    SELECT CurrentBuy = CASE
      WHEN x.RemainingNeedAdjusted > p.StockLevel
      THEN p.StockLevel
      ELSE x.RemainingNeedAdjusted
    END
  ) y
  WHERE p.StockLevel > 0
    AND b.RemainingNeed > 0
)
SELECT
  GoodID,
  ConditionID,
  SupplierStocklistItemID,
  Quantity,
  SupplierID,
  SupplierCurrencyID,
  CostPrice,
  RetailerID
FROM buys
WHERE PriceRank > 0
ORDER BY
  GoodID,
  ConditionID,
  PriceRank

基本上,CTE 形成的行与您的查询插入的行几乎相同#supplierpurchasesbase,除了它还具有用作内部变量的辅助列。(不过,它们不会被最终的 SELECT 拉动。)

锚点部分基于表形成一组 0 数量记录#needordered,以及辅助列的初始值。递归部分包含所有逻辑:计算要购买的数量,为下一次迭代更新“剩余需求”数量,检查是否需要下一次迭代。

已经做出了某些假设,如果它们与您的实际情况不符,我希望您能够找到解决方法。例如,数量、包装大小被假定为整数,并且部分逻辑依赖于此,因为它使用整数除法。还假设PriceRank是一个从 1 开始的整数序列,每个 都是唯一的(GoodID, ConditionID)

可以在 SQL Fiddle 上找到、测试、修改和测试此脚本以及最小的测试设置。

于 2012-05-02T21:04:43.883 回答
0

问题是双重的:更新语法不应该是:

UPDATE  CUR_AVAILABLE
SET StockLevel = @StockLevel - @Buy
WHERE   CURRENT OF CUR_AVAILABLE

相反,语法应该是:

UPDATE  #orderedprices
SET StockLevel = @StockLevel - @Buy
WHERE   CURRENT OF CUR_AVAILABLE

此外,为了可更新,临时表需要有一个主键:

ALTER TABLE #orderedprices ADD CONSTRAINT PRIMARY KEY CLUSTERED (RowCtr)

吸取了教训,我想,但找到解决方案确实让我有些悲伤!

于 2012-05-02T10:53:42.553 回答