680

のテーブル構造を仮定しますMyTable(KEY, datafield1, datafield2...)

多くの場合、既存のレコードを更新するか、存在しない場合は新しいレコードを挿入します。

基本的に:

IF (key exists)
  run update command
ELSE
  run insert command

これを書くための最良のパフォーマンスの方法は何ですか?

4

23 に答える 23

420

トランザクションを忘れないでください。パフォーマンスは良好ですが、単純な (IF EXISTS..) アプローチは非常に危険です。
複数のスレッドが挿入または更新を実行しようとすると、簡単に主キー違反が発生する可能性があります。

@Beau Crawford と @Esteban が提供するソリューションは、一般的なアイデアを示していますが、エラーが発生しやすいものです。

デッドロックと PK 違反を回避するには、次のようなものを使用できます。

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

また

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran
于 2008-09-20T15:06:11.063 に答える
395

非常によく似た以前の質問に対する私の詳細な回答を参照してください

@Beau Crawfordは SQL 2005 以下では良い方法ですが、 rep を付与する場合はSO it の最初の男に行く必要があります。唯一の問題は、挿入の場合、まだ 2 つの IO 操作であることです。

MS Sql2008mergeは、SQL:2003 標準から導入されています。

merge tablename with(HOLDLOCK) as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

これは本当に 1 つの IO 操作ですが、ひどいコードです :-(

于 2008-10-28T15:09:45.307 に答える
196

UPSERT を実行します。

UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key

IF @@ROWCOUNT = 0
   MyTable (FieldA) 値 (@FieldA) に挿入

http://en.wikipedia.org/wiki/Upsert

于 2008-09-20T15:04:03.923 に答える
115

多くの人が を使用することを提案しますが、使用しないようMERGEに注意してください。デフォルトでは、複数のステートメント以上に並行性と競合状態から保護されず、他の危険をもたらします。

この「より単純な」構文が利用可能であっても、私はまだこのアプローチを好みます (簡潔にするためにエラー処理は省略されています)。

BEGIN TRANSACTION;

UPDATE dbo.table WITH (UPDLOCK, SERIALIZABLE) 
  SET ... WHERE PK = @PK;

IF @@ROWCOUNT = 0
BEGIN
  INSERT dbo.table(PK, ...) SELECT @PK, ...;
END

COMMIT TRANSACTION;

多くの人がこの方法を提案します:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
  UPDATE ...
END
ELSE
BEGIN
  INSERT ...
END
COMMIT TRANSACTION;

ただし、これにより、更新する行を見つけるためにテーブルを 2 回読み取る必要がある場合があることを確認できます。最初のサンプルでは、​​行を見つける必要があるのは 1 回だけです。(どちらの場合も、最初の読み取りで行が見つからない場合、挿入が発生します。)

他の人はこのように提案します:

BEGIN TRY
  INSERT ...
END TRY
BEGIN CATCH
  IF ERROR_NUMBER() = 2627
    UPDATE ...
END CATCH

ただし、ほとんどすべての挿入が失敗するまれなシナリオを除いて、最初に防止できたはずの例外を SQL Server にキャッチさせる以外の理由がない場合、これは問題になります。私はここで多くを証明します:

于 2014-01-18T20:12:46.380 に答える
68
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

編集:

悲しいかな、私自身の不利益であっても、select なしでこれを行うソリューションは、1 つ少ないステップでタスクを達成するため、より優れているように思われることを認めなければなりません。

于 2008-09-20T15:02:30.603 に答える
39

一度に複数のレコードを UPSERT する場合は、ANSI SQL:2003 DML ステートメント MERGE を使用できます。

MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])

「 SQL Server 2005 での MERGE ステートメントの模倣」を確認してください。

于 2008-09-20T15:49:03.817 に答える
11

これについてコメントするのはかなり遅いですが、MERGE を使用したより完全な例を追加したいと思います。

このような Insert+Update ステートメントは通常 "Upsert" ステートメントと呼ばれ、SQL Server で MERGE を使用して実装できます。

非常に良い例がここにあります: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

上記では、ロックと同時実行のシナリオについても説明しています。

参考までに以下に引用します。

ALTER PROCEDURE dbo.Merge_Foo2
      @ID int
AS

SET NOCOUNT, XACT_ABORT ON;

MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
      ON f.ID = new_foo.ID
WHEN MATCHED THEN
    UPDATE
            SET f.UpdateSpid = @@SPID,
            UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
    INSERT
      (
            ID,
            InsertSpid,
            InsertTime
      )
    VALUES
      (
            new_foo.ID,
            @@SPID,
            SYSDATETIME()
      );

RETURN @@ERROR;
于 2010-01-21T05:47:51.407 に答える
6

それは使用パターンによって異なります。詳細に迷うことなく、使用状況の全体像を見る必要があります。たとえば、レコードが作成された後の使用パターンが 99% の更新である場合、「UPSERT」が最適なソリューションです。

最初の挿入 (ヒット) の後は、すべて単一ステートメントの更新であり、ifs や buts はありません。挿入の「where」条件が必要です。そうしないと、重複が挿入され、ロックを処理したくなくなります。

UPDATE <tableName> SET <field>=@field WHERE key=@key;

IF @@ROWCOUNT = 0
BEGIN
   INSERT INTO <tableName> (field)
   SELECT @field
   WHERE NOT EXISTS (select * from tableName where key = @key);
END
于 2015-11-07T22:35:50.173 に答える
6

ステートメントを使用できますMERGE。このステートメントは、存在しない場合はデータを挿入し、存在する場合は更新するために使用されます。

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`
于 2016-10-12T18:03:17.917 に答える
4

UPDATE if-no-rows-updated の次に INSERT ルートに進む場合は、最初に INSERT を実行して競合状態を防ぐことを検討してください (間に DELETE がないと仮定します)。

INSERT INTO MyTable (Key, FieldA)
   SELECT @Key, @FieldA
   WHERE NOT EXISTS
   (
       SELECT *
       FROM  MyTable
       WHERE Key = @Key
   )
IF @@ROWCOUNT = 0
BEGIN
   UPDATE MyTable
   SET FieldA=@FieldA
   WHERE Key=@Key
   IF @@ROWCOUNT = 0
   ... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END

競合状態を回避することは別として、ほとんどの場合、レコードが既に存在する場合、これにより INSERT が失敗し、CPU が浪費されます。

SQL2008 以降では、おそらく MERGE を使用することをお勧めします。

于 2011-08-25T06:43:09.590 に答える
2

最初に更新を試みてから挿入を試みた場合、競合状態は本当に問題になりますか? キーkeyの値を設定したい 2 つのスレッドがあるとします。

スレッド 1: 値 = 1
スレッド 2: 値 = 2

競合状態のシナリオ例

  1. キーが定義されていません
  2. 更新でスレッド 1 が失敗する
  3. 更新でスレッド 2 が失敗する
  4. スレッド 1 またはスレッド 2 の 1 つだけが挿入に成功します。例: スレッド 1
  5. 他のスレッドは挿入に失敗します (重複キーのエラーあり) - スレッド 2。

    • 結果: 挿入する 2 つの踏み板の「最初」が値を決定します。
    • 望ましい結果: データを書き込む 2 つのスレッド (更新または挿入) の最後のスレッドが値を決定する必要があります。

しかし; マルチスレッド環境では、OS スケジューラがスレッドの実行順序を決定します。上記のシナリオでは、この競合状態が発生し、実行順序を決定したのは OS でした。つまり、「スレッド 1」または「スレッド 2」がシステムの観点から「最初」だったというのは誤りです。

スレッド 1 とスレッド 2 の実行時間が非常に近い場合、競合状態の結果は重要ではありません。唯一の要件は、スレッドの 1 つが結果の値を定義する必要があることです。

実装について: update に続いて insert がエラー「duplicate key」になる場合、これは成功として扱われるべきです。

また、もちろん、データベース内の値が最後に書き込んだ値と同じであると想定してはなりません。

于 2011-07-20T11:07:43.033 に答える
2

MS SQL Server 2008 では、MERGE ステートメントが導入されました。これは、SQL:2003 標準の一部であると私は信じています。多くの人が示しているように、1 つの行のケースを処理することは大したことではありませんが、大規模なデータセットを処理する場合、カーソルが必要であり、すべてのパフォーマンスの問題が発生します。大規模なデータセットを処理する場合、MERGE ステートメントは非常に歓迎されます。

于 2008-10-12T12:25:23.580 に答える
1

以下のソリューションを試してみましたが、挿入ステートメントの同時要求が発生したときに機能します。

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert table (key, ...)
   values (@key, ...)
end
commit tran
于 2015-07-03T12:14:10.490 に答える
1

sprocs を直接実行しているこれらの悪意のあるユーザーからの恐怖から誰もが HOLDLOCK-s にジャンプする前に :-)設計によって新しい PK-s の一意性を保証する必要があることを指摘させてください(ID キー、Oracle のシーケンスジェネレーター、一意のインデックス)外部 ID、インデックスでカバーされるクエリ)。それが問題のアルファとオメガです。それがない場合、ユニバースの HOLDLOCK-s があなたを救うことはありません。それがある場合は、最初の選択で UPDLOCK を超えるものは必要ありません (または最初に更新を使用します)。

Sproc は通常、非常に制御された条件下で、信頼できる呼び出し元 (中間層) を想定して実行されます。つまり、単純な upsert パターン (更新 + 挿入またはマージ) で重複した PK が検出された場合、これは中間層またはテーブルの設計にバグがあることを意味し、そのような場合に SQL が障害を叫び、レコードを拒否するのは良いことです。この場合に HOLDLOCK を設定すると、パフォーマンスが低下するだけでなく、例外を処理し、潜在的に欠陥のあるデータを取り込むことになります。

そうは言っても、MERGEまたはUPDATEを使用してからINSERTを使用すると、最初の選択に(UPDLOCK)を追加することを覚えておく必要がないため、サーバー上でより簡単になり、エラーが発生しにくくなります。また、小さなバッチで挿入/更新を行っている場合は、トランザクションが適切かどうかを判断するためにデータを知る必要があります。無関係なレコードの単なるコレクションであり、追加の「エンベロープ」トランザクションは有害です。

于 2010-07-21T01:19:07.270 に答える
0

このクエリを使用できます。すべての SQL Server エディションで動作します。シンプルで明快です。ただし、2 つのクエリを使用する必要があります。MERGEが使えないなら使える

    BEGIN TRAN

    UPDATE table
    SET Id = @ID, Description = @Description
    WHERE Id = @Id

    INSERT INTO table(Id, Description)
    SELECT @Id, @Description
    WHERE NOT EXISTS (SELECT NULL FROM table WHERE Id = @Id)

    COMMIT TRAN

注: 否定的な回答について説明してください

于 2012-08-13T09:47:02.530 に答える
-1

SQL Server 2008 では、MERGE ステートメントを使用できます。

于 2008-09-20T15:39:39.843 に答える
-3

if exists ... else ... を実行するには、少なくとも 2 つの要求を実行する必要があります (1 つは確認用、もう 1 つはアクションを実行するため)。次のアプローチでは、レコードが存在する場合は 1 つだけ、挿入が必要な場合は 2 つ必要です。

DECLARE @RowExists bit
SET @RowExists = 0
UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123
IF @RowExists = 0
  INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx')
于 2008-09-20T15:07:36.210 に答える
-3

私は通常、最初に存在することを確認し、次に正しいパスが何であれ、他のポスターのいくつかが言ったことを行います。これを行う際に覚えておくべきことの 1 つは、SQL によってキャッシュされた実行計画が、いずれかのパスに対して最適ではない可能性があるということです。これを行う最善の方法は、2 つの異なるストアド プロシージャを呼び出すことだと思います。

最初の SP:
存在する場合
   SecondSP を呼び出す (UpdateProc)
そうしないと
   ThirdSP を呼び出す (InsertProc)

さて、私は自分自身のアドバイスにはあまり従わないので、それは控えめに考えてください。

于 2008-09-20T15:08:03.540 に答える
-3

ADO.NET を使用する場合、DataAdapter がこれを処理します。

自分で処理したい場合は、次の方法があります。

キー列に主キー制約があることを確認してください。

次にあなた:

  1. 更新を行う
  2. キーを持つレコードが既に存在するために更新が失敗した場合は、挿入を行います。更新が失敗しない場合は、完了です。

逆の方法で行うこともできます。つまり、最初に挿入を行い、挿入が失敗した場合は更新を行います。更新は挿入よりも頻繁に行われるため、通常は最初の方法の方が適しています。

于 2008-09-20T15:08:51.090 に答える
-10

選択を行い、結果が得られた場合は更新し、そうでない場合は作成します。

于 2008-09-20T15:02:26.627 に答える