2008-08-17 21 views
89

レコードが存在する場合に更新を行うストアドプロシージャを作成しました。それ以外の場合は、挿入が行われます。この方法でそれを書くの背後にある私のロジックは、更新が暗黙のは、WHERE句を使用して選択し実行し、それが0を返した場合、その後の挿入が行われることであるSQL Serverにストアドプロシージャを格納します。

update myTable set [email protected], [email protected] where [email protected] 
if @@rowcount = 0 
insert into myTable (Col1, Col2) values (@col1, @col2) 

:それは次のようになります。

このようにする代わりに、selectを実行し、返された行数に基づいて更新または挿入を行うこともできます。これは非効率的だと考えました。なぜなら、更新を行う場合、2つの選択(最初の明示的な選択呼び出しと2番目の暗黙的な更新)が発生するからです。 procが挿入を行う場合、効率に違いはありません。

私のロジックはここに聞こえますか? これは、挿入と更新をストアドプロシージャに組み合わせる方法ですか?

答えて

56

これは最適な方法です。これはupsert/mergeと呼ばれています。

Importance of UPSERT - from sqlservercentral.com:我々は代わりにEXISTS UPSERTを使用 場合

我々上記ケース内のすべての更新については、テーブルから1回の 追加の読み取りを削除します。 残念なことにインサートの場合、 UPSERTとIF EXISTSの両方の方法では、テーブル上の同じ読み取り数の が使用されます。 したがって、 のチェックは、 の追加I/Oを正当化する正当な理由がある場合は、 がある場合にのみ行う必要があります。 に最適化された方法は、 が DBでできるだけ読みにくいことを確認することです。

の更新を試みることをお勧めします。 更新の影響を受ける行がない場合は、挿入します。ほとんどの 状況では、行はすでに であり、1つのI/Oは になります。

編集: このパターンとどのようにそれが安全動作させるために問題について学ぶためにthis answerをチェックアウトして、リンクのブログ投稿してください。

+1

まあ、少なくとも1つの質問に答えました。その質問のコードがすでに私のように思えたので、私はコードを追加しませんでした。私はそれをトランザクションに入れますが、更新のために分離レベルを考慮しませんでした。あなたの答えにそれを指摘してくれてありがとう! – binOr

6

MERGEは、ところでSQL Server 2008の新機能の1つです。

+0

とあなたは絶対にこの使いにくい本物のナンセンスを使うべきです。 良い例がここにあります - https://www.mssqltips.com/sqlservertip/1704/using-merge-in-sql-server-to-insert-update-and-delete-at-the-same-time/ –

8

SQL Server 2000/2005で使用する場合は、元のコードをトランザクションに入れて、データが同時のシナリオで一貫していることを確認する必要があります。

BEGIN TRANSACTION Upsert 
update myTable set [email protected], [email protected] where [email protected] 
if @@rowcount = 0 
insert into myTable (Col1, Col2) values (@col1, @col2) 
COMMIT TRANSACTION Upsert 

これにより、パフォーマンスが向上しますが、データの整合性が確保されます。

すでに提案されているように、利用可能な場合は、MERGEを使用する必要があります。

3

UPSERTの大きなファンは、本当に管理するコードを削減します。私はそれを行う別の方法です:入力パラメータの1つはIDです.IDがNULLまたは0の場合はINSERT、それ以外の場合は更新です。アプリケーションはIDがあるかどうかを知っていると仮定しているので、すべての状況で動作することはありませんが、実行すると半分に削減されます。

1

ロジックは健全だと思われますが、特定のプライマリキーを渡した場合、挿入を防ぐためにコードを追加することを検討してください。

更新プログラムがレコードに影響を与えない場合は常に挿入を行いますが、「UPSERT」を実行する前に誰かがレコードを削除するとどうなりますか?これで、更新しようとしていたレコードが存在しないため、代わりにレコードが作成されます。おそらくあなたが探していた行動ではないでしょう。

3

あなたはSQL 2008でのマージを行っていない場合、あなたはそれを変更する必要があります。

場合@@ ROWCOUNT = 0と@@エラー= 0

そうでない場合は更新がそれを何らかの理由で失敗した場合失敗した文の行数が0であるため、後で挿入しようとします。

5

トランザクションで実行するだけでなく、高い分離レベルも必要です。実際には、デフォルトの分離レベルはRead Commitedであり、このコードにはSerializableが必要です。

SET transaction isolation level SERIALIZABLE 
BEGIN TRANSACTION Upsert 
UPDATE myTable set [email protected], [email protected] where [email protected] 
if @@rowcount = 0 
    begin 
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2) 
    end 
COMMIT TRANSACTION Upsert 

@@エラーチェックとロールバックを追加するとよいでしょう。

+0

@Munish Goyalデータベースでは複数のコマンドと前提がパラレルで実行されるためです。次に、他のスレッドは、更新が実行された直後および挿入が実行される前に行を挿入できます。 –

46

post on my blogは、安全で安全なパターンをお読みください。多くの考慮事項があり、この質問に対する受け入れられた答えは安全ではありません。

すぐに回答するには、次のパターンを試してみてください。それはSQL 2000以上で正常に動作します。 SQL 2005はエラー処理を提供し、他のオプションを開き、SQL 2008はMERGEコマンドを提供します。

begin tran 
    update t with (serializable) 
    set hitCount = hitCount + 1 
    where pk = @id 
    if @@rowcount = 0 
    begin 
     insert t (pk, hitCount) 
     values (@id,1) 
    end 
commit tran 
+0

ブログ記事では、存在チェックでWITH(updlock、serializable)ヒントを使用して結論づけます。ただし、MSDNの読み取り状態は次のとおりです。 "UPDLOCK - トランザクションが完了するまで更新ロックを取得および保持することを指定します。"これは、トランザクションの残りの部分で更新ロックが保持されるため、何かを誤解してしまったため、シリアライズ可能なヒントは不要ですか? –

1

修正ディママレンコポスト:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET COL1 = @col1, 
     COL2 = @col2 
WHERE ID = @ID 

IF @@rowcount = 0 
    BEGIN 
     INSERT INTO MYTABLE 
        (ID, 
        COL1, 
        COL2) 
     VALUES  (@ID, 
        @col1, 
        @col2) 
    END 

IF @@Error > 0 
    BEGIN 
     INSERT INTO MYERRORTABLE 
        (ID, 
        COL1, 
        COL2) 
     VALUES  (@ID, 
        @col1, 
        @col2) 
    END 

COMMIT TRANSACTION UPSERT 

あなたがエラーをトラップすることができますし、失敗した挿入テーブルにレコードを送信します。
私たちはWSDL経由で送信されたデータを取り込み、可能であれば内部的に固定しているので、これを行う必要がありました。

関連する問題