web-dev-qa-db-ja.com

開始トランザクションと終了トランザクションのあるカーソル

ループの2番目の繰り返しにハングし続ける次のスクリプトがあります。

BEGIN
BEGIN TRY
        BEGIN TRANSACTION
        DECLARE @itemID int

        DECLARE LoopCursor CURSOR FOR
        SELECT DISTINCT i.ItemID
        FROM Inventory i
        INNER JOIN Items it ON i.ItemID = it.ItemID
        WHERE i.ItemID IN (226, 231, 232, 233, 234, 235, 245, 247 ,249 ,250 ,253 ,254 ,255 ,257 ,258 ,268 ,270 ,271 ,273 ,286 ,287 ,291 ,293 ,299 ,303 ,304,
                            305, 306, 307, 308, 310, 311, 312, 313, 314, 316, 322, 323, 324, 331, 332, 333, 334, 335, 338, 339, 340, 341, 342, 343, 345, 346 ,347)
        AND it.[Serializable] = 0
        ORDER BY i.ItemID

        OPEN LoopCursor

        FETCH NEXT FROM LoopCursor
        INTO @itemID

        WHILE @@FETCH_STATUS = 0

            --Drop Temp tables if they exist.
            IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
               DROP TABLE #TEMP

            IF OBJECT_ID('tempdb..#INV_TEMP') IS NOT NULL
               DROP TABLE #INV_TEMP

            BEGIN
                    DECLARE @inventoryID int = 0,
                            @quantity int = 0,
                            @statusID int = 10,
                            @maxInvID int       
                    SET @inventoryID = (SELECT MAX(InventoryID) AS InventoryID FROM Inventory WHERE Inventory.ItemID = @itemID)

                    --Store Assets in temp table.
                    SELECT MAX(AssetID) AS AssetID, RoomID, COUNT(*) AS Quantity
                    INTO #TEMP
                    FROM Assets
                    WHERE InventoryID IN (SELECT InventoryID
                                            FROM Inventory
                                            WHERE ItemID = @itemID)
                    GROUP BY RoomID

                    --Update Assets with new Quantity value.
                    UPDATE Assets
                    SET Quantity = t.Quantity
                    FROM Assets a
                    INNER JOIN #TEMP t ON a.RoomID = t.RoomID
                    INNER JOIN Inventory i ON a.InventoryID = i.InventoryID
                    WHERE i.ItemID = @itemID

                    --Delete all other Assets with the same ItemID and that's not in the temp table.
                    DELETE a
                    FROM Assets a
                    INNER JOIN Inventory i ON a.InventoryID = i.InventoryID
                    INNER JOIN #TEMP t ON a.RoomID = t.RoomID
                    WHERE i.ItemID = @itemID
                    AND a.AssetID NOT IN (SELECT AssetID FROM #TEMP)

                    --Update Assets to have all the same InventoryID.
                    UPDATE Assets
                    SET Assets.InventoryID = @inventoryID, DateUpdated = GETDATE()
                    FROM Assets a
                    INNER JOIN Inventory i ON a.InventoryID = i.InventoryID
                    INNER JOIN #TEMP t ON a.RoomID = t.RoomID
                    WHERE i.ItemID = @itemID
                    AND a.AssetID IN (SELECT AssetID FROM #TEMP)

                    --Clean up Inventory now.

                    --Delete all Inventory with specific Statuses.
                    DELETE
                    FROM Inventory
                    WHERE ItemID = @itemID
                    AND StatusID IN (3, 6, 12, 14)

                    --Store Inventory records in a temp table
                    SELECT MAX(InventoryID) AS InventoryID, ItemID, StatusID, COUNT(*) AS Quantity
                    INTO #INV_TEMP
                    FROM Inventory
                    WHERE ItemID = @itemID
                    GROUP BY ItemID, StatusID

                    SELECT @maxInvID = MAX(InventoryID)
                    FROM #INV_TEMP

                    SELECT @quantity = t.Quantity
                    FROM #INV_TEMP t
                    WHERE StatusID = @statusID

                    --If there are no more of these items in inventory, change the status.
                    IF(@quantity = 0)
                        SET @statusID = 17

                    --Update the Quantity and Status for the given InventoryID and ItemID.
                    UPDATE Inventory
                    SET Quantity = @quantity,
                        StatusID = @statusID,
                        DateUpdated = GETDATE()
                    WHERE Inventory.InventoryID = @maxInvID 
                    AND Inventory.ItemID = @itemID

                    --Delete all the other Inventory records with the same ItemID
                    DELETE
                    FROM Inventory
                    WHERE InventoryID <> @maxInvID
                    AND ItemID = @itemID

            FETCH NEXT FROM LoopCursor
            INTO @itemID
        END

    CLOSE LoopCursor
    DEALLOCATE LoopCursor
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;

        DECLARE @ErrorNumber INT = ERROR_NUMBER();
        DECLARE @ErrorLine INT = ERROR_LINE();
        DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
        DECLARE @ErrorSeverity INT = ERROR_SEVERITY();
        DECLARE @ErrorState INT = ERROR_STATE();

        PRINT 'Actual error number: ' + CAST(@ErrorNumber AS VARCHAR(10));
        PRINT 'Actual line number: ' + CAST(@ErrorLine AS VARCHAR(10));

        RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH
END

私はカーソルの領域が得意ではありませんが、WHILEブロック内のすべてがItemIDに基づいて実行される必要があることを私が起こす必要があります。このスクリプトを2つのファイルに分離できます。1つはアセットを更新するだけで、もう1つはインベントリテーブルを更新するだけで、それぞれ期待どおりに実行されます。ぶら下がっている全体にカーソルを合わせた瞬間に見えます。

短い話は、同じItemIDを持つが異なるシリアル番号を持つ多くの在庫レコードがあるということです。私たちはこれをやめ、今では特定のItemIDの数量値のみを単一のInventoryレコードに表示したいと考えています。同じことは、InventoryIDとInventoryテーブルを結合することを除いて、アセットについても言えます。したがって、設定されている関係のため、最初に資産部分を更新し、次に在庫部分を最後に更新する必要があります。

どんな助けでも大歓迎です。

1
Robert

このコードを見るだけで、「明らかに良くない」と飛び出すのは、WHILEループを開始するが、IFの前に2つのBEGINステートメントがあることです。

WHILE @@FETCH_STATUS = 0

   --Drop Temp tables if they exist.
   IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
      DROP TABLE #TEMP

   IF OBJECT_ID('tempdb..#INV_TEMP') IS NOT NULL
      DROP TABLE #INV_TEMP

   BEGIN

その効果は、次のステートメントのみがWHILEの一部であり、次の無限ループを実行することです。

IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
      DROP TABLE #TEMP

追加のFETCHステートメントに到達していないため、@@FETCH_STATUSは更新されません。

BEGINWHILE @@FETCH_STATUS = 0の直後と最初のIFの前に移動します。

WHILE @@FETCH_STATUS = 0
BEGIN

   --Drop Temp tables if they exist.
   IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
      DROP TABLE #TEMP

   IF OBJECT_ID('tempdb..#INV_TEMP') IS NOT NULL
      DROP TABLE #INV_TEMP

さらに、カーソルのBEGIN TRAN/COMMIToutsideがあるのはなぜですか?これにより、すべてのItemIDsの処理が、各@ItemIDごとのステートメントだけではなく、単一の操作になります。これは本当に欲しいですか?または、個々の@ItemIDとそのすべての操作をアトミック操作にしたいが、それでも他のItemIDsから分離したいですか?それらを分離したい場合(これはより可能性が高いようです)、BEGIN TRANBEGINWHILEのすぐ内側に移動し、COMMITFETCH NEXT FROM LoopCursorの直前に移動します。ループの終わり。

3
Solomon Rutzky