2009-03-12 48 views
189

Tôi cần viết một thủ tục lưu trữ T-SQL để cập nhật một hàng trong bảng. Nếu hàng không tồn tại, hãy chèn nó vào. Tất cả các bước này được bao bọc bởi một giao dịch.Kiểm tra xem có tồn tại một hàng hay không, nếu không hãy chèn

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

Tôi là mới đối 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 đang đi đúng đường phải 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?) 
+0

có thể trùng lặp với [Solu tions cho INSERT HOẶC CẬP NHẬT trên SQL Server] (http://stackoverflow.com/questions/108403/solutions-for-insert-or-update-on-sql-server) –

+0

câu hỏi liên quan - https://stackoverflow.com/ câu hỏi/21889843/unique-constraint-vs-checking-before-insert – Steam

Trả lời

129

Tôi giả định 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, như cách bạn làm việc có thể hãy đăng ký một chuyến bay, vì nó sẽ chèn một dòng mới khi có 10 vé tối đa và bạn đang đặt 20.

+0

Có. Có 1 hàng trên mỗi chuyến bay. Nhưng mã của bạn thực hiện lệnh SELECT nhưng không kiểm tra xem chuyến bay có đầy đủ trước khi cập nhật hay không. làm như thế nào? –

+49

-1 Đây không phải là chủ đề an toàn. –

+0

Do điều kiện chủng tộc, điều này chỉ đúng nếu mức độ cách ly giao dịch hiện tại là Serializable. –

2

Đây là điều tôi vừa mới phả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 
+5

-1 Đây không phải là chủ đề an toàn. –

+0

@TheTXI - Bạn có thể làm cho chủ đề này an toàn không? – Steam

1

Bạn có thể sử dụng chức năng Merge để đạt được. Nếu không, bạn có thể làm:

declare @rowCount int 

select @[email protected]@RowCount 

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

mã sai chính tả! roWcount –

+0

MERGE chỉ hoạt động trên máy chủ SQL 2008 trở lên. – Steam

136

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

Dưới đây là triển khai hoạt động khi sử dụng MERGE
- Kiểm tra xem chuyến bay có đầy đủ trước khi thực hiện cập nhật hay không.

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 
+3

Ngoài ra, hãy xem tại sao bạn có thể thích [WITH (HOLDLOCK)] (http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx) cho MERGE đó . –

+3

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

+2

MERGE không có WITH (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 [Là MERGE một tuyên bố nguyên tử trong SQL2008?] (Http://stackoverflow.com/questions/9871644/is-merge-an-atomic-statement-in-sql2008) – James

58

đèo UPDLOCK, cột chèo, gợi ý HOLDLOCK khi kiểm tra cho 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 */ 

Các UPDLOCK gợi ý buộc truy vấn để có một khóa cập nhật trên hàng nếu nó đã tồn tại, ngăn chặn các giao dịch khác từ sửa đổi nó cho đến khi bạn cam kết hoặc quay trở lại.

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

Đề xuất khóa hàng buộc mức độ chi tiết khóa thành mức hàng thay vì cấp 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 đang cố gắng cập nhật các hàng không liên quan trong cùng một trang. và việc tăng chi phí khóa - bạn nên tránh sử dụng số lượng lớn các khóa cấp hàng trong một giao dịch đơn lẻ).

Xem http://msdn.microsoft.com/en-us/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 cho bạn khả năng miễn dịch chống lại một khóa mã hóa giao dịch khác trên thứ gì đó trước khi bạn nhận được nó.Bạn nên thử và nhân tố SQL của bạn để giữ khóa trong thời gian ngắn nhất có thể bằng cách cam kết 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à bigint, vì băm bên trong trên SQL Server bị thoái hóa đối với các giá trị 64 bit (các giá trị khóa khác nhau có thể được băm 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 là giả định 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, tức là đối với một câu lệnh cập nhật? Sau đó, nó có thể là khôn ngoan để hiển thị mã ở trên bằng cách sử dụng bắt đầu kết thúc khối đánh dấu để ngăn chặn người mới từ sao chép và dán mã của bạn và vẫn nhận được nó sai. –

+0

Có vấn đề gì nếu PK của tôi là một varchar (NOT max though) hoặc kết hợp ba cột VARCHAR? – Steam

+0

Tôi đã đặt câu hỏi liên quan đến câu trả lời này tại - https://stackoverflow.com/questions/21945850/a-thread-safe-way-to-check-if-a-row-exists-before-inserting-is-my -code-correct Câu hỏi là mã này có thể được sử dụng để chèn hàng triệu hàng. – Steam

33

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 là dễ dàng.

INSERT INTO TableName (col1,col2) 
SELECT @par1, @par2 
    WHERE NOT EXISTS (SELECT col1,col2 FROM TableName 
        WHERE [email protected] AND [email protected]) 

Ví dụ:

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

Giải thích:

(1) CHỌN col1, col2 TỪ TableName ĐÂU col1 = @ par1 VÀ col2 = @ par2 Nó chọn từ TableName tìm kiếm giá trị

(2) CHỌN @ par1, @ par2 KHÔNG NƠI Sẽ mất nếu không tồn tại (1) s ubquery

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

+0

Mã của bạn không bao giờ cập nhật và hàng hiện tại. – Ethan

+0

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

0

Giải pháp đầy đủ bên dưới (bao gồm cấu trúc con trỏ). Rất cám ơn Cassius Porcus vì mã số begin trans ... commit 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

cuối cùng tôi đã có thể chèn một hàng, với điều kiện là nó không 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 địa chỉ:

http://www.postgresql.org/message-id/[email protected]

+0

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

-3
INSERT INTO table (column1, column2, column3) 
SELECT $column1, $column2, $column3 
EXCEPT SELECT column1, column2, column3 
FROM table 
+0

INSERT INTO bảng (column1, column2, column3) SELECT $ column1, $ column2, $ column3 TRỪ CHỌN cột1, cột2, cột 3 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 để giải thích câu trả lời này thêm vào câu trả lời hiện có không? – francis

0
INSERT INTO Database.dbo.Table SELECT * FROM Database.dbo.Table 
WHERE ID not in (select ID from Database.dbo.Table) 
Các vấn đề liên quan