【问题标题】:Need to use a nested cursor and getting "invalid object name"需要使用嵌套游标并获取“无效对象名称”
【发布时间】:2012-05-03 05:04:43
【问题描述】:

首先,让我立即声明,我很清楚游标通常是邪恶的,不应该使用 - 我一直在使用集合,但无法提出基于集合的解决方案这个特殊的问题。如果你告诉我去做一些基于集合的操作,我完全赞成,如果你能告诉我你将如何编写这个特定的问题。

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

我已经将我需要购买的物品清单拉入#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_NEEDEDWHILE 循环之外定义CUR_AVAILABLE 光标,我尝试不关闭/释放光标等。这些似乎都不起作用。

这里有什么我出错的想法(除了不使用集合,除非你有基于集合的解决方案)?

【问题讨论】:

    标签: sql-server-2008 tsql cursor


    【解决方案1】:

    以下查询使用递归 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 拉取。)

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

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

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

    【讨论】:

    • 谢谢你,安德烈!我会试一试,如果我可以让它复制两个光标的功能,那么我会将你的标记为答案 - 它肯定比处理光标更可取(我想我可能不得不写 3 in过去 15 年,这就是为什么我在让这些人工作时遇到这么麻烦的原因)!
    【解决方案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)
    

    我猜是吸取了教训,但找到解决方案确实让我很伤心!

    【讨论】:

      猜你喜欢
      • 2021-12-15
      • 1970-01-01
      • 2013-06-18
      • 2017-04-11
      • 1970-01-01
      • 2016-05-15
      • 1970-01-01
      • 2018-07-13
      • 1970-01-01
      相关资源
      最近更新 更多