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 を使用しているため、真のセットベースのソリューションとは見なされません。それにもかかわらず、私はそれがあなたの2つのカーソルよりも優れたパフォーマンスを発揮することを期待しています(または、少なくとも試してみる価値があります):

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 によってプルされません。)

#needorderedアンカー部分は、補助列の初期値とともに、テーブルに基づいて数量 0 のレコードのセットを形成します。再帰部分にはすべてのロジックが含まれています。購入する数量を計算し、次の反復のために「残りの必要量」を更新し、次の反復が必要かどうかをチェックします。

一定の仮定がなされています。実際の状況と一致しない場合は、それらを回避できることを願っています。たとえば、数量、パックサイズは整数であると想定され、ロジックの一部は整数除算を使用するため、それに依存しています。また、 はPriceRank1 から始まる整数のシーケンスであり、 ごとに一意であると想定され(GoodID, ConditionID)ます。

このスクリプトと最小限のテスト セットアップは、 SQL Fiddleで検索、テスト、変更、およびテストできます。

于 2012-05-02T21:04:43.883 に答える
0

問題は 2 つあります: 更新構文は次のようであってはなりません。

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 に答える