Kiểm tra nếu một hàng tồn tại, nếu không chèn


237

Tôi cần phải viết một thủ tục lưu sẵn T-SQL cập nhật một hàng trong bảng. Nếu hàng không tồn tại, chèn nó. Tất cả các bước này được bao bọc bởi một giao dịch.

Đây là một hệ thống đặt phòng, vì vậy nó phải là nguyên tử và đáng tin cậy . Nó phải trả lại đúng nếu giao dịch được cam kết và chuyến bay đã đặt.

Tôi chưa quen với T-SQL và không chắc chắn về cách sử dụng @@rowcount. Đây là những gì tôi đã viết cho đến bây giờ. Tôi có đang đi đúng đường không? Tôi chắc chắn là một vấn đề dễ dàng cho bạn.

-- 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?)


Câu trả lời:


158

Hãy xem lệnh MERGE . Bạn có thể làm UPDATE, INSERTDELETEtrong một tuyên bố.

Đây là một triển khai hoạt động bằng cách sử dụng MERGE
- Nó kiểm tra xem chuyến bay có đầy đủ hay không trước khi thực hiện cập nhật, nếu không thì sẽ chèn.

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

Và sau đó ...

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
Ngoài ra, hãy xem lý do tại sao bạn có thể thích VỚI (HOLDLOCK) cho MERGE đó.
Eugene Ryabtsev

4
Tôi nghĩ MERGE được hỗ trợ sau năm 2005 (vì vậy 2008+).
samis

3
MERGE mà không CÓ VỚI (UPDLOCK) có thể có các vi phạm chính, điều này sẽ rất tệ trong trường hợp này. Xem [Is MERGE một tuyên bố nguyên tử trong SQL2008?] ( Stackoverflow.com/questions/9871644/... )
James

156

Tôi giả sử một hàng duy nhất cho mỗi chuyến bay? Nếu vậy:

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

Tôi giả sử những gì tôi đã nói, vì cách làm của bạn có thể đặt trước một chuyến bay, vì nó sẽ chèn một hàng mới khi có tối đa 10 vé và bạn đang đặt 20 vé.


Đúng. Có 1 hàng trên mỗi chuyến bay. Nhưng mã của bạn thực hiện CHỌN nhưng không kiểm tra xem chuyến bay đã đầy chưa trước khi CẬP NHẬT. làm như thế nào?

2
Do điều kiện cuộc đua, nó chỉ đúng nếu mức cô lập giao dịch hiện tại là Nối tiếp.
Jarek Przygódzki

1
@Martin: Câu trả lời tập trung vào câu hỏi trong tầm tay. Từ tuyên bố riêng của OP "Tất cả các bước này được bao bọc bởi một giao dịch". Nếu giao dịch được thực hiện chính xác, vấn đề an toàn của luồng không phải là vấn đề.
Gregory A Beamer

14
@GregoryABeamer - Chỉ cần dán nó ở BEGIN TRAN ... COMMITmức cô lập mặc định sẽ không giải quyết được vấn đề. OP quy định rằng nguyên tử và độ tin cậy là yêu cầu. Câu trả lời của bạn không giải quyết điều này dưới bất kỳ hình dạng hoặc hình thức.
Martin Smith

2
Điều này có an toàn cho chuỗi không nếu (UPDLOCK, HOLDLOCK) đã được thêm vào CHỌN : IF EXISTS (SELECT * FROM Bookings (UPDLOCK, HOLDLOCK) WHERE FLightID = @Id)?
Jim

67

Vượt qua gợi ý updlock, rowlock, Holdlock khi kiểm tra sự tồn tại của hàng.

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 */

Gợi ý updlock buộc truy vấn phải thực hiện khóa cập nhật trên hàng nếu nó đã tồn tại, ngăn các giao dịch khác sửa đổi nó cho đến khi bạn cam kết hoặc quay lại.

Gợi ý khóa giữ buộc truy vấn phải khóa phạm vi, ngăn các giao dịch khác thêm một hàng khớp với tiêu chí bộ lọc của bạn cho đến khi bạn cam kết hoặc quay lại.

Gợi ý khóa hàng buộc mức độ chi tiết của khóa thành mức hàng thay vì mức trang mặc định, vì vậy giao dịch của bạn sẽ không chặn các giao dịch khác cố cập nhật các hàng không liên quan trong cùng một trang (nhưng lưu ý đến sự đánh đổi giữa giảm tranh chấp và tăng khóa trên cao - bạn nên tránh lấy số lượng lớn các khóa cấp hàng trong một giao dịch).

Xem http://msdn.microsoft.com/en-us/l Library / ms187373.aspx để biết thêm thông tin.

Lưu ý rằng các khóa được thực hiện khi các câu lệnh đưa chúng được thực thi - việc gọi bắt đầu tran không cung cấp cho bạn khả năng miễn dịch đối với các giao dịch khác đang khóa các khóa trên một cái gì đó trước khi bạn nhận được nó. Bạn nên thử và tính hệ số SQL của mình để giữ các khóa trong thời gian ngắn nhất bằng cách thực hiện giao dịch càng sớm càng tốt (có được muộn, phát hành sớm).

Lưu ý rằng các khóa cấp hàng có thể kém hiệu quả hơn nếu PK của bạn là một vấn đề lớn, vì băm nội bộ trên SQL Server bị suy giảm cho các giá trị 64 bit (các giá trị khóa khác nhau có thể băm vào cùng một id khóa).


4
Khóa là RẤT quan trọng để tránh đặt trước quá nhiều. Có đúng không khi cho rằng một khóa được khai báo trong câu lệnh IF được giữ cho đến khi kết thúc câu lệnh IF, nghĩa là cho một câu lệnh cập nhật? Sau đó, có thể là khôn ngoan khi hiển thị mã ở trên bằng cách sử dụng các dấu chặn khối bắt đầu để ngăn người mới sao chép và dán mã của bạn và vẫn nhận được mã sai.
Simon B.

Có vấn đề gì không nếu PK của tôi là varchar (KHÔNG phải max) hoặc kết hợp ba cột VARCHAR?
Hơi nước

Tôi đã đặt câu hỏi liên quan đến câu trả lời này tại - stackoverflow.com/questions/21945850/ Câu hỏi là mã này có thể được sử dụng để chèn hàng triệu hàng không.
Hơi nước

Giải pháp này sẽ áp đặt quá nhiều khóa trong trường hợp khi nhiều luồng thường kiểm tra các hàng đã có sẵn. Tôi đoán điều này có thể được thực hiện xung quanh với một loại khóa được kiểm tra hai lần thông qua existskiểm tra bổ sung phòng ngừa mà không có gợi ý khóa.
Vadzim

38

Tôi đang viết giải pháp của mình. phương pháp của tôi không đứng 'nếu' hoặc 'hợp nhất'. phương pháp của tôi rất dễ dàng

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

Ví dụ:

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

Giải trình:

(1) CHỌN col1, col2 TỪ TableName WHERE col1 = @ par1 AND col2 = @ par2 Nó chọn từ các giá trị tìm kiếm TableName

(2) CHỌN @ par1, @ par2 Ở ĐÂU KHÔNG TRAO ĐỔI Phải mất nếu không tồn tại từ (1) truy vấn con

(3) Chèn vào giá trị bước TableName (2)


1
nó chỉ để chèn, không cập nhật.
Cem

Phương pháp này thực sự vẫn có thể thất bại vì việc kiểm tra sự tồn tại được thực hiện trước khi chèn - xem stackoverflow.com/a/3790757/1744834
Roman Pekar

3

Cuối cùng tôi đã có thể chèn một hàng, với điều kiện nó chưa tồn tại, sử dụng mô hình sau:

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 
    )
)

mà tôi tìm thấy tại:

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


1
Đây là một liên kết sao chép-dán chỉ trả lời ... phù hợp hơn như bình luận.
Ian

2

Đây là điều tôi vừa mới làm:

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

Bạn có thể sử dụng Chức năng Hợp nhất để đạt được. Nếu không bạn có thể làm:

declare @rowCount int

select @rowCount=@@RowCount

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

0

Giải pháp đầy đủ là bên dưới (bao gồm cả cấu trúc con trỏ). Rất cám ơn Cassius Porcus cho begin trans ... commitmã từ đăng ở trên.

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

Bảng INSERT INTO (cột1, cột2, cột3) CHỌN $ cột1, $ cột2, $ cột3 EXCEPT CHỌN cột1, cột2, cột3 từ bảng
Aaron

1
Có rất nhiều câu trả lời được đánh giá cao cho câu hỏi này. Bạn có thể giải thích những gì câu trả lời này thêm vào câu trả lời hiện có?
francis

-2

Cách tiếp cận tốt nhất cho vấn đề này trước tiên là làm cho cột cơ sở dữ liệu KHÔNG GIỚI HẠN

ALTER TABLE table_name ADD UNIQUE KEY

THEN INSERT IGNORE INTO table_name , giá trị sẽ không được chèn nếu nó dẫn đến một khóa trùng lặp / đã tồn tại trong bảng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.