107

レコードが存在する場合は更新を行い、そうでない場合は挿入を行うストアド プロシージャを作成しました。次のようになります。

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 番目の暗黙的な呼び出し) が発生するため、これは非効率的であると考えました。プロシージャが挿入を行う場合、効率に違いはありません。

ここで私の論理は正しいですか?これは、挿入と更新をストアド プロシージャに結合する方法ですか?

4

9 に答える 9

63

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

UPSERT の重要性 - sqlservercentral.com から:

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

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

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

于 2008-08-17T07:22:43.960 に答える
55

使用できる適切で安全なパターンについては、私のブログの投稿をお読みください。多くの考慮事項があり、この質問に対する受け入れられた答えは安全とは言えません。

簡単な答えを得るには、次のパターンを試してください。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
于 2008-10-11T09:04:47.527 に答える
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 を使用する必要があります。

于 2008-08-24T20:21:28.530 に答える
8

ちなみに、MERGE は SQL Server 2008 の新機能の 1 つです。

于 2008-08-17T07:24:48.200 に答える
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 チェックとロールバックも追加することをお勧めします。

于 2008-09-17T07:26:40.640 に答える
5

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

@@rowcount = 0 かつ @@error=0 の場合

それ以外の場合、何らかの理由で更新が失敗した場合、失敗したステートメントの行数が 0 であるため、後で挿入を試みます。

于 2008-09-02T20:13:39.647 に答える
3

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

于 2008-08-29T21:41:33.417 に答える
2

修正されたディマ・マレンコの投稿:

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

于 2012-05-01T14:27:53.913 に答える
1

あなたのロジックは正しいように見えますが、特定の主キーを渡した場合、挿入を防ぐためにコードを追加することを検討することをお勧めします。

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

于 2008-09-02T13:37:56.477 に答える