web-dev-qa-db-ja.com

ストアドプロシージャが不要なカーソル結果を返す

環境

OLTPメモリ内テーブルのマージコマンドをシミュレートするストアドプロシージャを作成しました。シミュレートされたマージを使用して、大量のデータを通常のテーブルからOLTP 1つ。

手順

これはコードです:

CREATE PROCEDURE [cache].[MoveInverterData] (@sourecInverterID bigint, @from datetime2(7))
AS   
BEGIN
    SET NOCOUNT ON;

    DECLARE @i INT = 1; 
    DECLARE @InverterID bigint, @Timestamp datetime2(7), @Status nvarchar(50);

    DECLARE Employee_Cursor CURSOR READ_ONLY FOR  
        SELECT [InverterID],[Timestamp],[Status]
        FROM [data].[InverterData]
        WHERE [InverterID] = @sourecInverterID AND [Timestamp] >= @from;  

    OPEN Employee_Cursor;

    FETCH NEXT FROM Employee_Cursor; 
    WHILE @@FETCH_STATUS = 0
    BEGIN
        FETCH NEXT FROM Employee_Cursor INTO @InverterID, @Timestamp, @Status;

        UPDATE [cache].[InverterData]
        SET [Status] = @Status
        WHERE [InverterID] = @InverterID AND [Timestamp] = @Timestamp;  

        -- if there was no row to update, insert
        IF @@ROWCOUNT=0  
            INSERT INTO [cache].[InverterData]
                ([InverterID],[Timestamp],[Status])
            VALUES
                (@InverterID, @Timestamp, @Status);  
    END

    CLOSE Employee_Cursor;  
    DEALLOCATE Employee_Cursor;

    RETURN(0)
END

OLTPテーブル(ターゲット) サポートされていませんMERGEコマンドであるため、カーソルはこのような行の範囲をアップサートします。

問題

SPを実行すると、最初に選択したカーソルFETCHの結果と、単一行数のリストが表示されます。

table result

message

質問

これは正常な動作であることは理解していますが、より簡単な結果を得るためにこれを抑制しようとしています。

私が欲しいもの:

  1. CURSORselectからのテーブル結果はありません
  2. 影響を受けた行数の合計は、すべての更新/挿入操作の単一の数としてカウントされます

空のFETCHは確かにエラーであり、ROWの結果の問題を解決します(ポイント1)。

今このように見えます:

CREATE PROCEDURE [cache].[MoveInverterData] (@sourecInverterID bigint, @from datetime2(7))
AS   
BEGIN
    SET NOCOUNT ON;

    DECLARE @i INT = 1; 
    DECLARE @InverterID bigint, @Timestamp datetime2(7), @Status nvarchar(50);

    DECLARE Employee_Cursor CURSOR READ_ONLY FOR  
        SELECT [InverterID],[Timestamp],[Status]
        FROM [data].[InverterData]
        WHERE [InverterID] = @sourecInverterID AND [Timestamp] >= @from;  

    OPEN Employee_Cursor;

    FETCH NEXT FROM Employee_Cursor INTO @InverterID, @Timestamp, @Status;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        UPDATE [cache].[InverterData]
        SET [Status] = @Status
        WHERE [InverterID] = @InverterID AND [Timestamp] = @Timestamp;  

        -- if there was no row to update, insert
        IF @@ROWCOUNT=0  
            INSERT INTO [cache].[InverterData]
                ([InverterID],[Timestamp],[Status])
            VALUES
                (@InverterID, @Timestamp, @Status); 

        FETCH NEXT FROM Employee_Cursor INTO @InverterID, @Timestamp, @Status;
    END

    CLOSE Employee_Cursor;  
    DEALLOCATE Employee_Cursor;

    RETURN(0)
END
1
Steffen Mangold

カーソル*を使用する代わりに、更新および挿入操作をセットベースの操作として明示的に記述します。

CREATE OR ALTER PROCEDURE cache.MoveInverterData
(
    @sourceInverterID bigint, 
    @from datetime2(7)
)
AS   
BEGIN
    SET XACT_ABORT, NOCOUNT ON;

    DECLARE @RowsAffected integer = 0;

    -- TODO: Add error handling

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    BEGIN TRANSACTION;

        -- Update existing rows
        UPDATE CID
        SET [Status] = DID.[Status]
        FROM [data].InverterData AS DID
        JOIN cache.InverterData AS CID
                WITH (SNAPSHOT)
            ON CID.InverterID = DID.InverterID
            AND CID.[Timestamp] = DID.[Timestamp]
        WHERE
            DID.InverterID = @sourceInverterID
            AND DID.[Timestamp] >= @from;

        SET @RowsAffected += @@ROWCOUNT;

        -- Insert new rows
        INSERT cache.InverterData
            (InverterID, [Timestamp], [Status])
        SELECT
            DID.InverterID,
            DID.[Timestamp],
            DID.[Status]
        FROM [data].InverterData AS DID
        WHERE
            DID.InverterID = @sourceInverterID
            AND DID.[Timestamp] >= @from
            AND NOT EXISTS
            (
                SELECT 1
                FROM cache.InverterData AS CID
                    WITH (SNAPSHOT)
                WHERE
                    CID.InverterID = DID.InverterID
                    AND CID.[Timestamp] = DID.[Timestamp]
            );

        SET @RowsAffected += @@ROWCOUNT;

    COMMIT TRANSACTION;

    -- TODO: Use the @RowsAffected result
END;

両方のテーブルのInverterID, [Timestamp]に(ハッシュ以外の)インデックスがあることを確認してください。

*他のデータベースエンジンとは異なり、SQLServerでは通常カーソルは非常に非効率的です。オーバーヘッド操作と行ごとの操作により、ほとんどの場合、セットベースのソリューションがより高速かつ効率的になります。

4
Paul White 9

または、ネイティブにコンパイルされたストアドプロシージャを可能な限り使用する場合は、メモリに最適化されたテーブルタイプを作成して、新しいデータを保持します。

CREATE TYPE dbo.InverterData AS TABLE
(
    RowID integer IDENTITY (1, 1) NOT NULL,
    InverterID  bigint NOT NULL,
    [Timestamp] datetime2(7) NOT NULL,
    [Status] nvarchar(50) NOT NULL,

    PRIMARY KEY NONCLUSTERED HASH (RowID)
        WITH (BUCKET_COUNT = 1024)
)
WITH (MEMORY_OPTIMIZED = ON);

次に、ネイティブにコンパイルされたプロシージャを記述して、新しいデータをマージします。

CREATE PROCEDURE cache.MoveInverterData_Native
    @InverterData dbo.InverterData READONLY
WITH NATIVE_COMPILATION, SCHEMABINDING
AS
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english')

    DECLARE @RowID integer = 1; 
    DECLARE @InverterID bigint, @Timestamp datetime2(7), @Status nvarchar(50);

    WHILE @RowID > 0
    BEGIN
        SELECT
            @InverterID = ID.InverterID,
            @Timestamp = ID.[Timestamp],
            @Status = ID.[Status]
        FROM @InverterData AS ID
        WHERE ID.RowID = @RowID;

        IF @@ROWCOUNT = 0
        BEGIN
            SET @RowID = 0
        END
        ELSE
        BEGIN
            UPDATE cache.InverterData
            SET [Status] = @Status
            WHERE InverterID = @InverterID
            AND [Timestamp] = @Timestamp;

            IF @@ROWCOUNT = 0
                INSERT cache.InverterData
                    (InverterID, [Timestamp], [Status])
                VALUES
                    (@InverterID, @Timestamp, @Status);

            SET @RowID += 1;
        END;
    END;
END;

そして、ソーステーブルからメモリ最適化テーブルタイプを設定するラッパープロシージャ:

CREATE OR ALTER PROCEDURE cache.MoveInverterData
(
    @sourceInverterID bigint, 
    @from datetime2(7)
)
AS   
BEGIN
    SET XACT_ABORT, NOCOUNT ON;

    DECLARE @InverterData dbo.InverterData;

    INSERT @InverterData
        (InverterID, [Timestamp], [Status])
    SELECT
        ID.InverterID,
        ID.[Timestamp],
        ID.[Status]
    FROM [data].InverterData AS ID
    WHERE
        ID.InverterID = @sourceInverterID
        AND ID.[Timestamp] >= @from;

    EXECUTE cache.MoveInverterData_Native @InverterData;
END;

次に、プロセス全体が次のような1回の呼び出しで呼び出されます。

EXECUTE cache.MoveInverterData
    @sourceInverterID = 1,
    @from = '20180601';
3
Paul White 9