행이 존재하는지 확인하고 그렇지 않으면 삽입


237

테이블의 행을 업데이트하는 T-SQL 저장 프로 시저를 작성해야합니다. 행이 존재하지 않으면 삽입하십시오. 이 모든 단계는 트랜잭션으로 래핑됩니다.

이것은 예약 시스템 용이므로 원자적이고 신뢰할 수 있어야합니다 . 트랜잭션이 커밋되고 항공편이 예약 된 경우 true를 반환해야합니다.

T-SQL에 새로운 사용하는 방법에 대한 확실하지,하고 @@rowcount. 이것이 내가 지금까지 쓴 것입니다. 내가 올바른 길에 있습니까? 나는 당신에게 쉬운 문제라고 확신합니다.

-- BEGIN TRANSACTION (HOW TO DO?)

UPDATE Bookings
 SET TicketsBooked = TicketsBooked + @TicketsToBook
 WHERE FlightId = @Id AND TicketsMax < (TicketsBooked + @TicketsToBook)

-- Here I need to insert only if the row doesn't exists.
-- If the row exists but the condition TicketsMax is violated, I must not insert 
-- the row and return FALSE

IF @@ROWCOUNT = 0 
BEGIN

 INSERT INTO Bookings ... (omitted)

END

-- END TRANSACTION (HOW TO DO?)

-- Return TRUE (How to do?)


답변:


158

MERGE 명령을 살펴보십시오 . 당신은 할 수있는 UPDATE, INSERTDELETE하나 개의 문장이다.

다음은 사용에 대한 실제 구현입니다 MERGE
. 업데이트를 수행하기 전에 비행이 꽉 찼는 지 확인하고 그렇지 않으면 삽입을 수행합니다.

if exists(select 1 from INFORMATION_SCHEMA.TABLES T 
              where T.TABLE_NAME = 'Bookings') 
begin
    drop table Bookings
end
GO

create table Bookings(
  FlightID    int identity(1, 1) primary key,
  TicketsMax    int not null,
  TicketsBooked int not null
)
GO

insert  Bookings(TicketsMax, TicketsBooked) select 1, 0
insert  Bookings(TicketsMax, TicketsBooked) select 2, 2
insert  Bookings(TicketsMax, TicketsBooked) select 3, 1
GO

select * from Bookings

그리고 ...

declare @FlightID int = 1
declare @TicketsToBook int = 2

--; This should add a new record
merge Bookings as T
using (select @FlightID as FlightID, @TicketsToBook as TicketsToBook) as S
    on  T.FlightID = S.FlightID
      and T.TicketsMax > (T.TicketsBooked + S.TicketsToBook)
  when matched then
    update set T.TicketsBooked = T.TicketsBooked + S.TicketsToBook
  when not matched then
    insert (TicketsMax, TicketsBooked) 
    values(S.TicketsToBook, S.TicketsToBook);

select * from Bookings

6
또한 해당 병합에 대해 WITH (HOLDLOCK) 을 원하는 이유를 확인하십시오 .
Eugene Ryabtsev 2016 년

4
MERGE는 2005 년 이후 (2008 년 이후) 지원된다고 생각합니다.
samis

3
WITH (UPDLOCK)가없는 MERGE는 기본 키 위반이있을 수 있으며이 경우에는 좋지 않습니다. [SQL2008에서 MERGE는 원 자성 선언문입니까?] ( stackoverflow.com/questions/9871644/… )
James

156

각 항공편에 대해 단일 행을 가정합니까? 그렇다면:

IF EXISTS (SELECT * FROM Bookings WHERE FLightID = @Id)
BEGIN
    --UPDATE HERE
END
ELSE
BEGIN
   -- INSERT HERE
END

최대 10 매의 티켓이 있고 20 명을 예약 할 때 새 행을 삽입하므로 항공편 예약을 초과 할 수 있기 때문에 내가 말한 것을 가정합니다.


예. 항공편 당 1 행이 있습니다. 그러나 코드는 SELECT를 수행하지만 업데이트 전에 비행이 가득 찼는 지 확인하지 않습니다. 이것을하는 방법?

2
경쟁 조건으로 인해 현재 트랜잭션 격리 수준이 Serializable 인 경우에만 정확합니다.
Jarek Przygódzki

1
@Martin : 대답은 당면한 문제에 초점을 맞추 었습니다. OP의 자체 문장에서 "이 모든 단계는 트랜잭션으로 래핑됩니다". 트랜잭션이 올바르게 구현되면 스레드 안전 문제는 문제가되지 않습니다.
Gregory A Beamer

14
@GregoryABeamer- BEGIN TRAN ... COMMIT기본 격리 수준 이하로 유지하면 문제가 해결되지 않습니다. OP는 원자적이고 신뢰할 수있는 것이 요구 사항 임을 명시했습니다 . 귀하의 답변은 어떤 형태 나 형태로도이를 해결하지 못합니다.
Martin Smith

2
(UPDLOCK, HOLDLOCK)이 SELECT에 추가 된 경우 스레드 안전 IF EXISTS (SELECT * FROM Bookings (UPDLOCK, HOLDLOCK) WHERE FLightID = @Id)합니까?
Jim

67

행의 존재를 테스트 할 때 업록, 행록, 홀드 락 힌트를 전달하십시오.

begin tran /* default read committed isolation level is fine */

if not exists (select * from Table with (updlock, rowlock, holdlock) where ...)
    /* insert */
else
    /* update */

commit /* locks are released here */

updlock 힌트는 쿼리가 이미 존재하는 경우 쿼리에서 업데이트 잠금을 수행하도록하여 커밋 또는 롤백 할 때까지 다른 트랜잭션이 수정하지 못하도록합니다.

holdlock 힌트는 쿼리가 범위 잠금을 수행하도록하여 커밋 또는 롤백 할 때까지 다른 트랜잭션이 필터 기준과 일치하는 행을 추가하지 못하게합니다.

rowlock 힌트는 기본 페이지 수준이 아닌 행 단위로 잠금 세분성을 강제 적용하므로 트랜잭션은 동일한 페이지에서 관련없는 행을 업데이트하려고하는 다른 트랜잭션을 차단하지 않습니다 (그러나 경합 감소와 잠금 오버 헤드-단일 트랜잭션에서 많은 수의 행 수준 잠금을 사용하지 않아야합니다.

자세한 내용은 http://msdn.microsoft.com/en-us/library/ms187373.aspx 를 참조하십시오.

잠금은 그것들을 실행하는 문장으로 간주됩니다. begin tran을 호출한다고해서 다른 트랜잭션이 잠기기 전에 잠금을 잠그는 것을 막을 수는 없습니다. 가능한 한 빨리 트랜잭션을 커밋 (늦게 획득하고 일찍 릴리스)하여 가능한 가장 짧은 시간 동안 잠금을 유지하도록 SQL을 시도하고 인수 분해해야합니다.

SQL Server의 내부 해시가 64 비트 값으로 퇴화되기 때문에 PK가 큰 경우 행 수준 잠금이 덜 효과적 일 수 있습니다 (다른 키 값이 동일한 잠금 ID로 해시 될 수 있음).


4
초과 예약을 피하려면 잠금이 매우 중요합니다. IF 문에 선언 된 잠금이 IF 문이 끝날 때까지, 즉 하나의 업데이트 문에 대해 유지된다고 가정하는 것이 맞습니까? 그런 다음 초보자 용 끝 마커를 사용하여 위의 코드를 표시하여 초보자가 코드를 복사하여 붙여 넣고 여전히 잘못하는 것을 방지하는 것이 좋습니다.
Simon B.

내 PK가 varchar (최대는 아니지만) 또는 세 개의 VARCHAR 열의 조합 인 경우 문제가 있습니까?
Steam

이 답변과 관련된 질문을 stackoverflow.com/questions/21945850/ 에서 만들었습니다. 질문 은이 코드를 사용하여 수백만 행을 삽입 할 수 있습니까 ?
Steam

이 솔루션은 많은 스레드가 이미 기존 행을 테스트하는 경우 너무 많은 잠금 오버 헤드를 부과합니다. exists잠금 힌트없이 예방 추가 검사 를 통해 일종의 이중 검사 잠금으로 해결할 수 있다고 생각 합니다.
Vadzim

38

내 솔루션을 작성 중입니다. 내 방법은 'if'또는 'merge'가 아닙니다. 내 방법은 쉽습니다.

INSERT INTO TableName (col1,col2)
SELECT @par1, @par2
   WHERE NOT EXISTS (SELECT col1,col2 FROM TableName
                     WHERE col1=@par1 AND col2=@par2)

예를 들어 :

INSERT INTO Members (username)
SELECT 'Cem'
   WHERE NOT EXISTS (SELECT username FROM Members
                     WHERE username='Cem')

설명:

(1) SELECT col1, col2 FROM TableName WHERE col1 = @ par1 AND col2 = @ par2 TableName에서 검색된 값에서 선택

(2) SELECT @ par1, @ par2 존재하지 않는 경우 (1) 하위 쿼리에서 존재하지 않는 경우 소요

(3) TableName에 삽입 (2) 단계 값


1
삽입 용이며 업데이트하지 않습니다.
Cem

실제로이 방법이 실패하기 때문에 삽입 전에 존재 여부 검사가 수행 될 수 있습니다.- stackoverflow.com
3790757/1744834

3

다음 모델을 사용하여 이미 존재하지 않는 조건에서 행을 삽입 할 수있었습니다.

INSERT INTO table ( column1, column2, column3 )
(
    SELECT $column1, $column2, $column3
      WHERE NOT EXISTS (
        SELECT 1
          FROM table 
          WHERE column1 = $column1
          AND column2 = $column2
          AND column3 = $column3 
    )
)

내가 찾은 것 :

http://www.postgresql.org/message-id/87hdow4ld1.fsf@stark.xeocode.com


1
이것은 복사-붙여 넣기 링크 전용 답변입니다 ... 의견에 더 적합합니다.
Ian

2

이것은 내가 최근에해야했던 일입니다.

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[cjso_UpdateCustomerLogin]
    (
      @CustomerID AS INT,
      @UserName AS VARCHAR(25),
      @Password AS BINARY(16)
    )
AS 
    BEGIN
        IF ISNULL((SELECT CustomerID FROM tblOnline_CustomerAccount WHERE CustomerID = @CustomerID), 0) = 0
        BEGIN
            INSERT INTO [tblOnline_CustomerAccount] (
                [CustomerID],
                [UserName],
                [Password],
                [LastLogin]
            ) VALUES ( 
                /* CustomerID - int */ @CustomerID,
                /* UserName - varchar(25) */ @UserName,
                /* Password - binary(16) */ @Password,
                /* LastLogin - datetime */ NULL ) 
        END
        ELSE
        BEGIN
            UPDATE  [tblOnline_CustomerAccount]
            SET     UserName = @UserName,
                    Password = @Password
            WHERE   CustomerID = @CustomerID    
        END

    END

1

병합 기능을 사용하여 달성 할 수 있습니다. 그렇지 않으면 당신은 할 수 있습니다 :

declare @rowCount int

select @rowCount=@@RowCount

if @rowCount=0
begin
--insert....

0

전체 솔루션은 다음과 같습니다 (커서 구조 포함). begin trans ... commit위 코드를 게시 해 주신 Cassius Porcus에게 감사드립니다 .

declare @mystat6 bigint
declare @mystat6p varchar(50)
declare @mystat6b bigint

DECLARE mycur1 CURSOR for

 select result1,picture,bittot from  all_Tempnogos2results11

 OPEN mycur1

 FETCH NEXT FROM mycur1 INTO @mystat6, @mystat6p , @mystat6b

 WHILE @@Fetch_Status = 0
 BEGIN

 begin tran /* default read committed isolation level is fine */

 if not exists (select * from all_Tempnogos2results11_uniq with (updlock, rowlock, holdlock)
                     where all_Tempnogos2results11_uniq.result1 = @mystat6 
                        and all_Tempnogos2results11_uniq.bittot = @mystat6b )
     insert all_Tempnogos2results11_uniq values (@mystat6 , @mystat6p , @mystat6b)

 --else
 --  /* update */

 commit /* locks are released here */

 FETCH NEXT FROM mycur1 INTO @mystat6 , @mystat6p , @mystat6b

 END

 CLOSE mycur1

 DEALLOCATE mycur1
 go

0
INSERT INTO [DatabaseName1].dbo.[TableName1] SELECT * FROM [DatabaseName2].dbo.[TableName2]
 WHERE [YourPK] not in (select [YourPK] from [DatabaseName1].dbo.[TableName1])

-2
INSERT INTO table ( column1, column2, column3 )
SELECT $column1, $column2, $column3
EXCEPT SELECT column1, column2, column3
FROM table

INSERT INTO 테이블 (column1, column2, column3) SELECT $ column1, $ column2, $ column3 EXCEPT SELECT column1, column2, column3 from table
Aaron

1
이 질문에 대한 많은 찬사를받은 답변이 있습니다. 이 답변이 기존 답변에 추가되는 내용을 자세히 설명해 주시겠습니까?
프랜시스

-2

이 문제에 대한 가장 좋은 방법은 먼저 데이터베이스 열을 UNIQUE로 만드는 것입니다.

ALTER TABLE table_name ADD UNIQUE KEY

THEN INSERT IGNORE INTO table_name , 중복 키 / 이미 테이블에 존재하면 값이 삽입되지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.