web-dev-qa-db-ja.com

挿入と更新用の別個のストアドプロシージャ?

Microsoft SQL Serverにテーブルがあります。更新が必要な場合と挿入が必要な場合があります。 2つのストアドプロシージャを記述できます。

InsertNewPerson
UpdatePertsonById

しかし、私は両方を行う1つのストアドプロシージャ(SetPerson)を作成することを考えていました(IDがある場合、それはupdate操作です。それ以外の場合はinsert)。

1つのストアドプロシージャ(1つだけを維持する)を作成する必要がありますか、それとも2つの異なるストアドプロシージャを作成する必要がありますか?

5
Royi Namir

私が理解している限り、ここで実際にUPSERTについて話しているのではなく、1つのストアドプロシージャで2つの異なるCRUD操作を組み合わせているだけです。

CREATE PROC InsertOrUpdateYourTable @Id int = NULL OUTPUT,
                                    @Foo INT,
                                    @Bar VARCHAR(10)
AS
    IF @Id IS NULL
      BEGIN
          INSERT INTO YourTable
                      (Foo,
                       Bar)
          VALUES      (@Foo,
                       @Bar)

          SET @Id = SCOPE_IDENTITY()
      END
    ELSE
      BEGIN
          UPDATE YourTable
          SET    Foo = @Foo,
                 Bar = @Bar
          WHERE  Id = @Id
      END 

これで私が目にする利点は、テーブル構造が変更された場合に2つの個別のパラメーターリストを維持する必要がないことです。欠点は、単一のストアドプロシージャに2つの責任があり、理解がやや難しいことです。

私は通常、それらを2つのストアドプロシージャに分離することを選択します。

RE:「アップサートの外観について詳しく教えてください」

CREATE PROC UpsertYourTable
@Id int,
@Foo int,
@Bar varchar(10)
AS
MERGE YourTable WITH (HOLDLOCK)  AS T
        USING ( VALUES ( @Id, @Foo, @Bar ) ) 
              AS source ( Id, Foo, Bar)
        ON ( T.Id = source.Id )
        WHEN MATCHED 
            THEN 
        UPDATE SET
                Foo = source.Foo ,
                Bar = source.Bar
        WHEN NOT MATCHED 
            THEN    
        INSERT  (Id, Foo , Bar)
               VALUES 
               (@Id, @Foo , @Bar);

これは、IdIDENTITY列ではなくなったと想定しています。 HOLDLOCKを使用する理由 ここで説明します

9
Martin Smith

1つのプロシージャを使用する場合は、 MERGEステートメント を使用できます。

マージコードの例:

create table testo(Id int identity(1,1) NOT NULL, somechar char(1), someint int, AddedTime datetime2(0), LastModifiedTime datetime2(0));
alter table testo add constraint [PK_testo] primary key(Id);    --Clustered index on target table.
--No index necessary on the source 'table' because we're creating it from parameters.

declare @Id int = 1;
declare @somechar char(1) = 'A';
declare @someint int = 42;

declare @results table (DMLAction sysname, Id int, somechar char(1), someint int );

MERGE dbo.testo AS Target
    USING
        (
            SELECT  
                @Id as Id,
                @somechar as somechar,
                @someint as someint
        ) AS SOURCE
    ON
        TARGET.Id = SOURCE.Id
    WHEN MATCHED
        THEN UPDATE SET 
            TARGET.somechar = SOURCE.somechar,
            TARGET.someint = SOURCE.someint,
            TARGET.LastModifiedTime = CURRENT_TIMESTAMP
    WHEN NOT MATCHED BY TARGET
        THEN INSERT
        (
            somechar,
            someint,
            AddedTime,
            LastModifiedTime
        )
        VALUES
        (
            SOURCE.somechar,
            SOURCE.someint,
            CURRENT_TIMESTAMP,
            CURRENT_TIMESTAMP
        )
    OUTPUT
        $action,
        inserted.Id,
        inserted.somechar,
        inserted.someint
    INTO
        @results;

select * from @results;

最大の欠点は、パフォーマンスが低下することです(ただし、 賢明なインデックス付けにより、パフォーマンスの問題を軽減できます )。それを打ち消す、いくつかの利点があります。

最初の利点は 監査ははるかに完全です です。必要な場合にSOURCEおよびTARGETレコードの値にアクセスできるためです。これは、INSERTまたはUPDATEのOUTPUT句からは取得できないものです。

このアプローチの2番目の利点は、個別のパラメーター(この簡単な例で行ったような)を使用する代わりに、データレイヤーに table-valued parameter を作成し、それをSOURCEテーブルとして使用できることです。つまり、理論的には、同じ呼び出しで複数のレコードを挿入/更新できます。確かに、同じプロシージャまたは2つの別々のストアドプロシージャで別々のINSERTステートメントとUPDATEステートメントを使用しても同じことができますが、これにより、2つではなく1つのT-SQLステートメントを維持できます。

最後に、必要な努力をしたい場合は、MergeのOUTPUT句の結果(上記のサンプルの@results)を取得し、それを.NET(またはアプリケーションレイヤーが何であれ)にフィードバックして、テーブルの結果。これは簡単なことではありませんが、もう一度Getを呼び出す必要はありません。また、複数のレコードを同時に挿入する場合、SCOPE_IDENTITY()を介してID列の結果を取得しようとする必要はありません。挿入された疑似テーブルには、すでに各レコードのID列の値が含まれています。

繰り返しになりますが、ほとんどの利点は、別々の挿入ステートメントと更新ステートメントで複製できることです。そのため、これは主にMERGEのパフォーマンスコストと、2箇所ではなく1箇所で正しく取得する必要があり、別々に行う必要がないことによるメンテナンスの利点に要約されます。ビジネスレイヤーまたはデータレイヤーにロジックを挿入/更新します。

3
Kevin Feasel