のテーブル構造を想定しますMyTable(KEY, datafield1, datafield2...)
。
多くの場合、既存のレコードを更新するか、存在しない場合は新しいレコードを挿入します。
基本的に:
IF (key exists)
run update command
ELSE
run insert command
これを書くのに最も良い方法は何ですか?
のテーブル構造を想定しますMyTable(KEY, datafield1, datafield2...)
。
多くの場合、既存のレコードを更新するか、存在しない場合は新しいレコードを挿入します。
基本的に:
IF (key exists)
run update command
ELSE
run insert command
これを書くのに最も良い方法は何ですか?
回答:
トランザクションを忘れないでください。パフォーマンスは良好ですが、単純な(IF EXISTS ..)アプローチは非常に危険です。
複数のスレッドがInsert-or-updateを実行しようとすると、主キー違反が簡単に発生します。
@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
@Beau CrawfordはSQL 2005以下では良い方法ですが、担当者を許可する場合は、最初の担当者に依頼してください。唯一の問題は、挿入の場合、2つのIO操作であることです。
MS Sql2008 merge
は、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操作にすぎませんが、ひどいコード:-(
upsert
、他のすべてのDBプロバイダーが代わりにサポートすることを決定したものではありません。upsert
それはT-SQLで唯一の非標準のキーワードのようにそうではありません-構文はとても少なくともMSがあまりにもそれをサポートしている必要があり、これを行うにはこれまでよりよい方法である
MERGE
。
HOLDLOCK
、同時実行性の高い状況ではマージ操作が必要です。
UPSERTを実行します。
UPDATE MyTable SET FieldA = @ FieldA WHERE Key = @ Key IF @@ ROWCOUNT = 0 INSERT INTO MyTable(FieldA)VALUES(@FieldA)
多くの人がを使用することを提案しますMERGE
が、私はそれを警告します。デフォルトでは、複数のステートメントよりも同時実行性や競合状態からユーザーを保護することはなく、他の危険も伴います。
http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/
この「より単純な」構文が利用できる場合でも、私はこのアプローチを優先しています(簡潔にするためにエラー処理は省略しています)。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE dbo.table 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
INSERT ...
END
COMMIT TRANSACTION;
ただし、これにより、更新する行を見つけるためにテーブルを2回読み取る必要がある場合があります。最初のサンプルでは、行を見つける必要があるのは一度だけです。(どちらの場合も、最初の読み取りから行が見つからない場合、挿入が発生します。)
他の人はこのように提案します:
BEGIN TRY
INSERT ...
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 2627
UPDATE ...
END CATCH
ただし、ほとんどすべての挿入が失敗するというまれなシナリオを除いて、SQL Serverに例外をキャッチさせる以外の理由で最初から防ぐことができれば、はるかにコストがかかる場合、これは問題になります。私はここで多くを証明します:
UPDATE target SET col = tmp.col FROM target INNER JOIN #tmp ON <key clause>; INSERT target(...) SELECT ... FROM #tmp AS t WHERE NOT EXISTS (SELECT 1 FROM target WHERE key = t.key);
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)
編集:
悲しいかな、私自身の不利益にも関わらず、selectなしでこれを行うソリューションは、1つの少ないステップでタスクを完了するため、より良いように思えます。
一度に複数のレコードを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ステートメントの模倣を確認してください。
これについてコメントするのはかなり遅いですが、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;
/*
CREATE TABLE ApplicationsDesSocietes (
id INT IDENTITY(0,1) NOT NULL,
applicationId INT NOT NULL,
societeId INT NOT NULL,
suppression BIT NULL,
CONSTRAINT PK_APPLICATIONSDESSOCIETES PRIMARY KEY (id)
)
GO
--*/
DECLARE @applicationId INT = 81, @societeId INT = 43, @suppression BIT = 0
MERGE dbo.ApplicationsDesSocietes WITH (HOLDLOCK) AS target
--set the SOURCE table one row
USING (VALUES (@applicationId, @societeId, @suppression))
AS source (applicationId, societeId, suppression)
--here goes the ON join condition
ON target.applicationId = source.applicationId and target.societeId = source.societeId
WHEN MATCHED THEN
UPDATE
--place your list of SET here
SET target.suppression = source.suppression
WHEN NOT MATCHED THEN
--insert a new line with the SOURCE table one row
INSERT (applicationId, societeId, suppression)
VALUES (source.applicationId, source.societeId, source.suppression);
GO
テーブルとフィールドの名前を必要なものに置き換えます。使用ON条件に注意してください。次に、DECLARE行の変数に適切な値(およびタイプ)を設定します。
乾杯。
UPDATE if-no-rows-updated then 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を使用することをお勧めします。
それは使用パターンによって異なります。詳細に迷うことなく、使用状況の全体像を確認する必要があります。たとえば、レコードの作成後に使用パターンが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
MS SQL Server 2008はMERGEステートメントを導入しています。これは、SQL:2003標準の一部だと思います。多くの人が1行のケースを処理することは大きな問題ではないことを示していますが、大きなデータセットを処理する場合は、カーソルが必要であり、それに伴うすべてのパフォーマンスの問題があります。MERGEステートメントは、大規模なデータセットを処理するときに非常に歓迎される追加です。
誰もがsprocを直接実行しているこれらの悪意のあるユーザーからの恐怖からHOLDLOCK-sにジャンプする前に、設計によって新しいPK-sの一意性(IDキー、Oracleのシーケンスジェネレーター、一意のインデックス)を保証する必要があることを指摘しておきます。外部ID、インデックスでカバーされるクエリ)。それが問題のアルファとオメガです。それがない場合は、宇宙のHOLDLOCK-sによって節約されることはありません。その場合、最初の選択時(または最初に更新を使用するとき)にUPDLOCK以外のものは必要ありません。
Sprocは通常、非常に制御された条件下で実行され、信頼できる呼び出し元(中間層)を想定しています。つまり、単純なアップサートパターン(更新+挿入またはマージ)で重複するPKが検出された場合、中間層またはテーブルの設計にバグがあることを意味し、SQLがそのような場合にエラーを通知してレコードを拒否するのは良いことです。この場合にHOLDLOCKを設定することは、パフォーマンスを低下させることに加えて、例外を食べ、欠陥のある可能性のあるデータを取り込むことと同じです。
そうは言っても、最初に選択するために(UPDLOCK)を追加することを覚えておく必要がないので、MERGEまたはUPDATEを使用してからINSERTを使用すると、サーバーで簡単になり、エラーが発生しにくくなります。また、小さなバッチで挿入/更新を行う場合は、トランザクションが適切かどうかを判断するためにデータを知る必要があります。それは単に関連のないレコードのコレクションであり、追加の「エンベロープ」トランザクションは有害になります。
最初に更新に続いて挿入を試みる場合、競合状態は本当に重要ですか?key keyの値を設定したい2つのスレッドがあるとします。
スレッド1:値= 1
スレッド2:値= 2
競合状態シナリオの例
他のスレッドは挿入で失敗します(エラー重複キー)-スレッド2。
だが; マルチスレッド環境では、OSスケジューラがスレッドの実行順序を決定します。この競合状態がある上記のシナリオでは、実行の順序を決定したのはOSでした。つまり、「スレッド1」または「スレッド2」がシステムの観点から「最初」だったと言うのは間違っています。
スレッド1とスレッド2の実行時間が非常に近い場合、競合状態の結果は重要ではありません。唯一の要件は、スレッドの1つが結果の値を定義することです。
実装の場合:更新に続いて挿入の結果、エラー「重複キー」が発生した場合、これは成功として処理する必要があります。
また、当然のことながら、データベースの値が最後に書き込んだ値と同じであると想定してはいけません。
SQL Server 2008では、MERGEステートメントを使用できます
このクエリを使用できます。すべての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
注:否定的な回答を説明してください
ADO.NETを使用する場合、DataAdapterがこれを処理します。
あなたがそれを自分で扱いたいなら、これは方法です:
キー列に主キー制約があることを確認してください。
次にあなた:
逆に行うこともできます。つまり、最初に挿入を行い、挿入が失敗した場合は更新を行います。更新は挿入よりも頻繁に行われるため、通常は最初の方法が適しています。
ifが存在する... 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')
私は通常、他のポスターのいくつかが言ったことを最初にそれが存在するかどうかを確認し、次に正しいパスが何であれ実行することに関して行います。これを行うときに覚えておかなければならないことの1つは、sqlによってキャッシュされた実行プランがいずれかのパスに対して最適ではない可能性があることです。これを行う最良の方法は、2つの異なるストアドプロシージャを呼び出すことです。
FirstSP: 存在する場合 SecondSPを呼び出す(UpdateProc) そうしないと ThirdSPを呼び出す(InsertProc)
さて、私は自分のアドバイスにはあまり従わないので、一粒の塩で服用してください。
選択を行い、結果が得られたら更新し、そうでなければ作成します。