SQL Serverに更新ストアドプロシージャを挿入する


104

レコードが存在する場合に更新を実行するストアドプロシージャを記述しました。それ以外の場合は挿入を実行します。次のようになります。

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

この方法でそれを書く私の背後にあるロジックは、更新がwhere句を使用して暗黙的な選択を実行し、それが0を返す場合、挿入が行われるということです。

この方法で行う代わりに、選択を行い、返された行数に基づいて更新または挿入を行う方法があります。これは、更新を行うと2つの選択(最初の明示的な選択呼び出しと更新の場所での2番目の暗黙的な呼び出し)が発生するため、非効率的であると考えました。プロシージャが挿入を行う場合、効率に違いはありません。

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

回答:


61

あなたの仮定は正しいです、これはそれを行うための最適な方法であり、upsert / mergeと呼ばれています。

UPSERTの重要性-sqlservercentral.comから

上記のケースの更新ごとに、EXISTSの代わりにUPSERTを使用する場合、テーブルから1つの追加の読み取りを削除します。残念ながら挿入では、UPSERTメソッドとIF EXISTSメソッドの両方がテーブルで同じ数の読み取りを使用します。したがって、存在の確認は、追加のI / Oを正当化する正当な理由がある場合にのみ行う必要があります。最適化された方法は、DBでの読み取りをできるだけ少なくすることです。

最善の方法は、更新を試みることです。更新の影響を受ける行がない場合は、挿入します。ほとんどの場合、行はすでに存在しており、必要なI / Oは1つだけです。

編集この回答とリンクされたブログ投稿をチェックして、このパターンの問題と安全に動作させる方法を確認してください。


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

54

安全で使いやすいパターンについては、私のブログ投稿をご覧ください。多くの考慮事項があり、この質問で受け入れられた答えは安全とはほど遠いものです。

簡単に答えるには、次のパターンを試してください。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

1
ブログ投稿では、存在チェックでWITH(updlock、serializable)ヒントを使用して結論を​​出します。ただし、MSDNを読むと、「UPDLOCK-トランザクションが完了するまで更新ロックを取得して保持することを指定します。」とにかく、トランザクションの残りの部分で更新ロックが保持されるため、シリアル化可能なヒントが不必要であることを意味しますか、それとも私は何かを誤解していますか?
Dan Def

10

SQL Server 2000/2005で使用する場合、元のコードをトランザクションで囲み、データが並行シナリオで一貫性を保つようにする必要があります。

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

これにより、追加のパフォーマンスコストが発生しますが、データの整合性は保証されます。

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



6

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

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
COMMIT TRANSACTION Upsert

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


@Munish Goyalデータベースでは、複数のコマンドと手順が並行して実行されるためです。次に、更新が実行された直後で挿入が実行される前に、他のスレッドが行を挿入できます。
Tomas Tintera

5

SQL 2008でマージを行わない場合は、次のように変更する必要があります。

@@ rowcount = 0および@@ error = 0の場合

そうでなければ、更新がなんらかの理由で失敗した場合、失敗したステートメントの行カウントが0であるため、後で挿入を試みます。


3

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


2

修正されたDima Malenkoの投稿:

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を介して送信されるデータを取得し、可能であれば内部で修正するためです。


1

ロジックは適切に見えますが、特定の主キーを渡した場合は、挿入を防ぐためにいくつかのコードを追加することを検討してください。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.