존재하지 않는 경우 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, 프로 시저 EmailsRebidbidInsert, 11 행
키워드 'WHERE'근처의 구문이 잘못되었습니다.


10
중복을 방지하기 위해이 검사에만 의존해서는 안되며 스레드 안전하지 않으며 경쟁 조건이 충족되면 복제본을 얻습니다. 고유 데이터가 실제로 필요한 경우 테이블에 고유 제한 조건을 추가 한 후 고유 제한 조건 위반 오류를 포착하십시오. 이 답변보기
GarethD

1
당신은 MERGE 쿼리를 사용하거나 (SELECT 문)이 존재하지 않으면 값 END 삽입을 시작할 수 있습니다
압둘 한난 Ijaz

이 검사를 릴레이해야하는지 여부에 따라 시나리오에 따라 다릅니다. 예를 들어 데이터를 "정적"테이블에 쓰는 배포 스크립트를 개발하는 경우 이는 문제가되지 않습니다.
AxelWass


2
@GarethD : "나사 안전하지 않음"은 무엇을 의미합니까? 우아하지는 않지만 나에게 맞는 것처럼 보입니다. 단일 insert명령문은 항상 단일 트랜잭션입니다. SQL Server가 하위 쿼리를 먼저 평가 한 다음 나중에 다시 잠금을 유지하지 않고 삽입을 수행하는 것처럼 아닙니다.
Ed Avis

답변:


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 (경쟁 조건)를 실행하기 전에 두 번째 연결이 IF NOT EXISTS 테스트를 통과 할 수 있기 때문에 여전히 실패 할 수 있습니다. 트랜잭션을 래핑해도 문제가 해결되지 않는 이유에 대한 좋은 대답 은 stackoverflow.com/a/3791506/1836776 을 참조하십시오 .


20
부하가 높은 경우 첫 번째 연결이 INSERT (경쟁 조건)를 실행하기 전에 두 번째 연결이 IF NOT EXISTS 테스트를 통과 할 수 있기 때문에 여전히 실패 할 수 있습니다. 트랜잭션을 래핑해도 문제가 해결되지 않는 이유에 대한 좋은 대답 은 stackoverflow.com/a/3791506/1836776 을 참조하십시오 .
Marc Durdin

11
EmailsRecebidos에서 1을 선택하십시오. De = _DE AND Assunto = @_ASSUNTO AND Data = @_DATA * 대신 1을 사용하는 것이 더 효율적입니다
Reno

1
전체에 쓰기 잠금을 설정하면 중복 가능성이 없습니다.
Kevin Finkenbinder

10
select *이 경우 @jazzcat 은 EXISTS절 에서 사용되고 있기 때문에 아무런 차이가 없습니다 . SQL Server는 항상이를 최적화하고 오랫동안 사용해 왔습니다. 나는 아주 나이가 많기 때문에 보통 이러한 쿼리를 작성 EXISTS (SELECT 1 FROM...)하지만 더 이상 필요하지 않습니다.
Loudenvier

16
왜 이런 간단한 질문이 확실성보다 더 의심을 일으키는가?
drowa

77

가장 빠른 방법을 찾는 사람들을 위해 최근 에 이러한 벤치 마크를 발견했습니다. "INSERT SELECT ... EXCEPT SELECT ..."를 사용하는 것이 5 천만 개 이상의 레코드 중에서 가장 빠른 것으로 판명 된 .

이 기사의 일부 샘플 코드는 다음과 같습니다 (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를 좋아합니다
Bryan

1
처음으로 EXCEPT를 사용했습니다. 간단하고 우아합니다.
jhowe

그러나 EXCEPT는 대량 작업에 효율적이지 않을 수 있습니다.
Aasish Kr. Sharma

예외는 그렇게 효율적이지 않습니다.
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

im는 그것의 더 멋진 때문에이 함께 갈 메신저
jokab

병합을 사용하고 싶지만 메모리 최적화 테이블에서는 작동하지 않습니다.
Don Sam

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

@FilipDeVos : true-가능성은 많지 않지만 여전히 가능성입니다. 좋은 지적.
marc_s

트랜잭션 내에서 둘 다 래핑하면 어떻게됩니까? 그 가능성을 막을 것인가? (나는 거래에 대한 전문가가 아니기 때문에 이것이 멍청한 질문이라면 용서해주십시오.)
David

1
트랜잭션이이 문제를 해결하지 못하는 이유에 대한 좋은 답변 은 stackoverflow.com/a/3791506/1836776 을 참조하십시오 ( @David).
Marc Durdin

IF 문에서 : 둘 이상의 행을 사용하더라도 필수 명령 행 수가 하나 인 경우 BEGIN & END를 사용할 필요가 없으므로 여기서 생략 할 수 있습니다.
Wessam El Mahdy

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

IF EXISTS를 제외하고 SQL Server 버전 (2012?)에 따라 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 문이 있는데 그 중 소수의 레코드가 이미 데이터베이스에 존재하는 경우 어떤 레코드인지 알 수 없습니다. 몇 백 개를 처리 한 후 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.