存在しない場合はSQL Server挿入


243

テーブルにデータを挿入したいが、データベースに存在しないデータのみを挿入したい。

これが私のコードです:

ALTER PROCEDURE [dbo].[EmailsRecebidosInsert]
  (@_DE nvarchar(50),
   @_ASSUNTO nvarchar(50),
   @_DATA nvarchar(30) )
AS
BEGIN
   INSERT INTO EmailsRecebidos (De, Assunto, Data)
   VALUES (@_DE, @_ASSUNTO, @_DATA)
   WHERE NOT EXISTS ( SELECT * FROM EmailsRecebidos 
                   WHERE De = @_DE
                   AND Assunto = @_ASSUNTO
                   AND Data = @_DATA);
END

そしてエラーは:

メッセージ156、レベル15、状態1、プロシージャEmailsRecebidosInsert、行11
キーワード 'WHERE'の近くの構文が正しくありません。


10
重複がないことを確認するために、このチェックだけに依存するべきではありません。スレッドセーフではなく、競合条件が満たされたときに重複が発生します。一意のデータが本当に必要な場合は、テーブルに一意の制約を追加し、一意の制約違反エラーをキャッチします。この回答を参照してください
GarethD 2014年

1
MERGEクエリを使用するか、存在しない場合(selectステートメント)挿入値の開始END
Abdul Hannan Ijaz

このチェックでリレーする必要があるかどうかは、シナリオによって異なります。たとえば、「静的」テーブルにデータを書き込むデプロイスクリプトを開発している場合、これは問題ではありません。
AxelWass 2016年

次のように「存在しない場合(select * from ...」のように使用できます。stackoverflow.com
a /

2
@GarethD:「スレッドセーフではない」とはどういう意味ですか?エレガントではないかもしれませんが、私には正しいように見えます。単一のinsertステートメントは常に単一のトランザクションです。これは、SQL Serverが最初にサブクエリを評価し、その後、ある時点で、ロックを保持せずに挿入を実行する場合とは異なります。
Ed Avis 2017

回答:


322

コードの下ではなく

BEGIN
   INSERT INTO EmailsRecebidos (De, Assunto, Data)
   VALUES (@_DE, @_ASSUNTO, @_DATA)
   WHERE NOT EXISTS ( SELECT * FROM EmailsRecebidos 
                   WHERE De = @_DE
                   AND Assunto = @_ASSUNTO
                   AND Data = @_DATA);
END

と置換する

BEGIN
   IF NOT EXISTS (SELECT * FROM EmailsRecebidos 
                   WHERE De = @_DE
                   AND Assunto = @_ASSUNTO
                   AND Data = @_DATA)
   BEGIN
       INSERT INTO EmailsRecebidos (De, Assunto, Data)
       VALUES (@_DE, @_ASSUNTO, @_DATA)
   END
END

更新:(@Marc Durdinの指摘に感謝)

最初の接続がINSERTを実行する前に、2番目の接続がIF NOT EXISTSテストに合格する可能性があるため(つまり、競合状態)、高負荷下でもこれは失敗することがあります。トランザクションをラップしても解決しない理由については、stackoverflow.com / a / 3791506/1836776を参照してください。


20
最初の接続がINSERTを実行する前に、2番目の接続がIF NOT EXISTSテストに合格する可能性があるため(つまり、競合状態)、高負荷下でもこれは失敗することがあります。トランザクションをラップしても解決しない理由については、stackoverflow.com / a / 3791506/1836776を参照してください。
Marc Durdin 2014

11
SELECT 1 FROM EmailsRecebidos WHERE De = @_DE AND Assunto = @_ASSUNTO AND Data = @_DATA *の代わりに1を使用する方が効率的です
Reno

1
全体に書き込みロックをかけると、重複する可能性がなくなります。
Kevin Finkenbinder 2016年

10
select *この場合の@jazzcat は、EXISTS句で使用されているため、何の違いもありません。SQL Serverは常に最適化し、古くから使用しています。私は非常に年をとっているので、通常これらのクエリを次のように記述しEXISTS (SELECT 1 FROM...)ますが、もう必要ありません。
Loudenvier

16
なぜこの種の単純な質問は確実性よりも疑念を生み出すのですか?
drowa

77

最速の方法を探している人のために、私は最近これらのベンチマーク出くわしました。明らかに "INSERT SELECT ... EXCEPT SELECT ..."を使用することが5000万以上のレコードで最速であることが判明しました。

以下は、記事のサンプルコードです(コードの3番目のブロックが最速でした)。

INSERT INTO #table1 (Id, guidd, TimeAdded, ExtraData)
SELECT Id, guidd, TimeAdded, ExtraData
FROM #table2
WHERE NOT EXISTS (Select Id, guidd From #table1 WHERE #table1.id = #table2.id)
-----------------------------------
MERGE #table1 as [Target]
USING  (select Id, guidd, TimeAdded, ExtraData from #table2) as [Source]
(id, guidd, TimeAdded, ExtraData)
    on [Target].id =[Source].id
WHEN NOT MATCHED THEN
    INSERT (id, guidd, TimeAdded, ExtraData)
    VALUES ([Source].id, [Source].guidd, [Source].TimeAdded, [Source].ExtraData);
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT id, guidd, TimeAdded, ExtraData from #table2
EXCEPT
SELECT id, guidd, TimeAdded, ExtraData from #table1
------------------------------
INSERT INTO #table1 (id, guidd, TimeAdded, ExtraData)
SELECT #table2.id, #table2.guidd, #table2.TimeAdded, #table2.ExtraData
FROM #table2
LEFT JOIN #table1 on #table1.id = #table2.id
WHERE #table1.id is null

6
EXCEPT SELECTが好き
ブライアン

1
初めてEXCEPTを使用しました。シンプルでエレガント。
jhowe 2017年

ただし、EXCEPTは一括操作には効率的でない場合があります。
Aasish Kr。シャルマ

EXCEPTはそれほど効率的ではありません。
Biswa

1
@Biswa:それらのベンチマークに従っていない。コードはサイトから入手できます。システムで実行して、結果の比較を確認してください。

25

私はマージを使用します:

create PROCEDURE [dbo].[EmailsRecebidosInsert]
  (@_DE nvarchar(50),
   @_ASSUNTO nvarchar(50),
   @_DATA nvarchar(30) )
AS
BEGIN
   with data as (select @_DE as de, @_ASSUNTO as assunto, @_DATA as data)
   merge EmailsRecebidos t
   using data s
      on s.de = t.de
     and s.assunte = t.assunto
     and s.data = t.data
    when not matched by target
    then insert (de, assunto, data) values (s.de, s.assunto, s.data);
END

イムその愛好家のために、この一緒に行く
jokab

マージを使用したいのですが、メモリ最適化テーブルでは機能しません。
ドン・サム

20

以下のコードを試してください

ALTER PROCEDURE [dbo].[EmailsRecebidosInsert]
  (@_DE nvarchar(50),
   @_ASSUNTO nvarchar(50),
   @_DATA nvarchar(30) )
AS
BEGIN
   INSERT INTO EmailsRecebidos (De, Assunto, Data)
   select @_DE, @_ASSUNTO, @_DATA
   EXCEPT
   SELECT De, Assunto, Data from EmailsRecebidos
END

11

INSERTコマンドはありませんWHERE句を-あなたはこのようにそれを記述する必要があります:

ALTER PROCEDURE [dbo].[EmailsRecebidosInsert]
  (@_DE nvarchar(50),
   @_ASSUNTO nvarchar(50),
   @_DATA nvarchar(30) )
AS
BEGIN
   IF NOT EXISTS (SELECT * FROM EmailsRecebidos 
                   WHERE De = @_DE
                   AND Assunto = @_ASSUNTO
                   AND Data = @_DATA)
   BEGIN
       INSERT INTO EmailsRecebidos (De, Assunto, Data)
       VALUES (@_DE, @_ASSUNTO, @_DATA)
   END
END

1
チェックと挿入の間に挿入が発生する場合があるため、この手順ではエラーを処理する必要があります。
Filip De Vos 2014年

@FilipDeVos:true-可能性、おそらくそれほど可能性は低いですが、それでも可能性があります。いい視点ね。
marc_s 2014年

トランザクション内で両方をラップするとどうなりますか?それは可能性をブロックしますか?(私はトランザクションの専門家ではないので、これが愚かな質問である場合はご容赦ください。)
David

1
参照stackoverflow.com/a/3791506/1836776をトランザクションは、@デビッドこれを解決しない理由についての良い答えを。
Marc Durdin、2014

IFステートメント:複数の行を使用した場合でも、必要なコマンド行が1行だけの場合は、BEGIN&ENDを使用する必要がないため、ここでは省略できます。
Wessam El Mahdy 2017年

11

SQL Server 2012でも同じことを行いましたが、うまくいきました

Insert into #table1 With (ROWLOCK) (Id, studentId, name)
SELECT '18769', '2', 'Alex'
WHERE not exists (select * from #table1 where Id = '18769' and studentId = '2')

4
もちろんうまくいきました。一時テーブルを使用しています(つまり、一時テーブルを使用するときに同時実行性について心配する必要はありません)。
drowa

6

SQL Serverのバージョン(2012?)によっては、IF EXISTS以外に、次のようにMERGEを使用することもできます。

ALTER PROCEDURE [dbo].[EmailsRecebidosInsert]
    ( @_DE nvarchar(50)
    , @_ASSUNTO nvarchar(50)
    , @_DATA nvarchar(30))
AS BEGIN
    MERGE [dbo].[EmailsRecebidos] [Target]
    USING (VALUES (@_DE, @_ASSUNTO, @_DATA)) [Source]([De], [Assunto], [Data])
         ON [Target].[De] = [Source].[De] AND [Target].[Assunto] = [Source].[Assunto] AND [Target].[Data] = [Source].[Data]
     WHEN NOT MATCHED THEN
        INSERT ([De], [Assunto], [Data])
        VALUES ([Source].[De], [Source].[Assunto], [Source].[Data]);
END

2

異なるSQL、同じ原則。存在しない場所の句が失敗した場合にのみ挿入する

INSERT INTO FX_USDJPY
            (PriceDate, 
            PriceOpen, 
            PriceLow, 
            PriceHigh, 
            PriceClose, 
            TradingVolume, 
            TimeFrame)
    SELECT '2014-12-26 22:00',
           120.369000000000,
           118.864000000000,
           120.742000000000,
           120.494000000000,
           86513,
           'W'
    WHERE NOT EXISTS
        (SELECT 1
         FROM FX_USDJPY
         WHERE PriceDate = '2014-12-26 22:00'
           AND TimeFrame = 'W')

-1

以下のコードで説明されているように、以下のクエリを実行して自分自身を確認します。

CREATE TABLE `table_name` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  `address` varchar(255) NOT NULL,
  `tele` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB;

レコードを挿入します。

INSERT INTO table_name (name, address, tele)
SELECT * FROM (SELECT 'Nazir', 'Kolkata', '033') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_name WHERE name = 'Nazir'
) LIMIT 1;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0

SELECT * FROM `table_name`;

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Nazir  | Kolkata   | 033  |
+----+--------+-----------+------+

ここで、同じレコードをもう一度挿入してみます。

INSERT INTO table_name (name, address, tele)
SELECT * FROM (SELECT 'Nazir', 'Kolkata', '033') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_name WHERE name = 'Nazir'
) LIMIT 1;

Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Nazir  | Kolkata   | 033  |
+----+--------+-----------+------+

別のレコードを挿入します。

INSERT INTO table_name (name, address, tele)
SELECT * FROM (SELECT 'Santosh', 'Kestopur', '044') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_name WHERE name = 'Santosh'
) LIMIT 1;

Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0

SELECT * FROM `table_name`;

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Nazir  | Kolkata   | 033  |
|  2 | Santosh| Kestopur  | 044  |
+----+--------+-----------+------+

1
これはMySQLの問題ではなく、SQL Serverの問題ですか?
Douglas Gaskell

はい、MySQL用です。
vadiraj jahagirdar

-2

GOコマンドを使用できます。これにより、エラー後にSQLステートメントの実行が再開されます。私の場合、数千のINSERTステートメントがあり、それらのレコードの一部が既にデータベースに存在していますが、どれがどれかわかりません。数100を処理した後INSERT、レコードが既に存在するため実行できないエラーメッセージで実行が停止することがわかりました。かなり煩わしいですが、これをGO解決しました。それは最速のソリューションではないかもしれませんが、速度は私の問題ではありませんでした。

GO
INSERT INTO mytable (C1,C2,C3) VALUES(1,2,3)
GO
INSERT INTO mytable (C1,C2,C3) VALUES(4,5,6)
 etc ...

GOバッチセパレーターとは?重複レコードの防止には役立ちません。
デールK
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.