Giải pháp cho CHERTN HOẶC CẬP NHẬT trên SQL Server


598

Giả sử cấu trúc bảng của MyTable(KEY, datafield1, datafield2...).

Thường thì tôi muốn cập nhật một bản ghi hiện có hoặc chèn một bản ghi mới nếu nó không tồn tại.

Bản chất:

IF (key exists)
  run update command
ELSE
  run insert command

Cách thực hiện tốt nhất để viết này là gì?



27
Đối với bất kỳ ai gặp câu hỏi này lần đầu tiên - vui lòng đảm bảo đọc tất cả các câu trả lời và nhận xét của họ. Tuổi đôi khi có thể dẫn đến thông tin sai lệch ...
Aaron Bertrand

1
Cân nhắc sử dụng toán tử EXCEPT, được giới thiệu trong SQL Server 2005.
Tarzan

Câu trả lời:


370

đừng quên các giao dịch. Hiệu suất là tốt, nhưng cách tiếp cận đơn giản (NẾU EXISTS ..) là rất nguy hiểm.
Khi nhiều luồng sẽ cố gắng thực hiện Chèn hoặc cập nhật, bạn có thể dễ dàng bị vi phạm khóa chính.

Các giải pháp được cung cấp bởi @Beau Crawford & @Esteban cho thấy ý tưởng chung nhưng dễ bị lỗi.

Để tránh bế tắc và vi phạm PK, bạn có thể sử dụng một cái gì đó như thế này:

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

hoặc là

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran

1
Câu hỏi yêu cầu giải pháp hiệu quả nhất chứ không phải là an toàn nhất. Trong khi một giao dịch thêm bảo mật cho quá trình, nó cũng thêm một chi phí.
Luke Bennett

31
Cả hai phương pháp này vẫn có thể thất bại. Nếu hai luồng đồng thời thực hiện cùng một hàng trên cùng một hàng, thì luồng đầu tiên sẽ thành công, nhưng lần chèn thứ hai sẽ thất bại do vi phạm khóa chính. Một giao dịch không đảm bảo rằng chèn sẽ thành công ngay cả khi cập nhật thất bại vì bản ghi tồn tại. Để đảm bảo rằng bất kỳ số lượng giao dịch đồng thời nào sẽ thành công, bạn PHẢI sử dụng khóa.
Jean Vincent

7
@aku bất kỳ lý do nào bạn đã sử dụng gợi ý bảng ("với (xxxx)") trái ngược với "THIẾT LẬP CẤP PHÂN PHỐI CẤP CẤP CẤP" ngay trước BEGIN TRAN của bạn?
EBarr

4
@CashCow, chiến thắng cuối cùng, đây là điều mà INSERT hoặc CẬP NHẬT phải làm: cái đầu tiên chèn, cái thứ hai cập nhật bản ghi. Thêm khóa cho phép điều này xảy ra trong một khung thời gian rất ngắn, ngăn ngừa lỗi.
Jean Vincent

1
Tôi luôn nghĩ rằng gợi ý sử dụng khóa là xấu và chúng ta nên để Microsoft Internal engine ra lệnh khóa. Đây có phải là ngoại lệ rõ ràng cho quy tắc?

381

Xem câu trả lời chi tiết của tôi cho một câu hỏi trước đó rất giống

@Beau Crawford's là một cách hay trong SQL 2005 trở xuống, mặc dù nếu bạn cấp đại diện, bạn nên tìm đến người đầu tiên để SO . Vấn đề duy nhất là để chèn vẫn còn hai thao tác IO.

MS Sql2008 giới thiệu mergetừ tiêu chuẩn SQL: 2003:

merge tablename with(HOLDLOCK) as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

Bây giờ nó thực sự chỉ là một hoạt động IO, nhưng mã khủng khiếp :-(


10
@Ian Boyd - vâng, đó là cú pháp của tiêu chuẩn SQL: 2003, không phải upsertlà tất cả các nhà cung cấp DB khác quyết định hỗ trợ thay thế. Các upsertcú pháp là một cách xa đẹp hơn để làm điều này, vì vậy tại MS rất ít nên đã ủng hộ nó quá - nó không giống như đó là từ khóa phi tiêu chuẩn duy nhất trong T-SQL
Keith

1
bất kỳ bình luận về gợi ý khóa trong câu trả lời khác? (sẽ tìm ra sớm, nhưng nếu đó là cách được đề xuất, tôi khuyên bạn nên thêm nó vào câu trả lời)
eglasius

25
Xem tại đây weblogs.sqlteam.com/dang/archive/2009/01/31/ đối với câu trả lời về cách ngăn chặn các điều kiện chủng tộc gây ra lỗi có thể xảy ra ngay cả khi sử dụng MERGEcú pháp.
Seph

5
@Seph đó là một bất ngờ thực sự - phần nào thất bại của Microsoft ở đó: -SI đoán điều đó có nghĩa là bạn cần một HOLDLOCKhoạt động hợp nhất trong các tình huống đồng thời cao.
Keith

11
Câu trả lời này thực sự cần được cập nhật để giải thích cho nhận xét của Seph về việc nó không an toàn cho chủ đề nếu không có GIỮ. Theo bài đăng được liên kết, MERGE ngầm lấy ra một khóa cập nhật, nhưng giải phóng nó trước khi chèn các hàng, điều này có thể gây ra tình trạng chủng tộc và vi phạm khóa chính khi chèn. Bằng cách sử dụng HOLDLOCK, các khóa được giữ cho đến sau khi quá trình chèn xảy ra.
Triynko

169

Làm một UPSERT:

CẬP NHẬT MyTable SET FieldA = @ FieldA WHERE Key = @ Key

NẾU @@ ROWCOUNT = 0
   XÁC NHẬN GIÁ TRỊ MyTable (FieldA) (@FieldA)

http://en.wikipedia.org/wiki/Upsert


7
Vi phạm khóa chính không nên xảy ra nếu bạn áp dụng các ràng buộc chỉ mục duy nhất phù hợp. Toàn bộ điểm của ràng buộc là ngăn chặn các hàng trùng lặp khỏi mọi sự kiện xảy ra. Không quan trọng có bao nhiêu luồng đang cố gắng chèn, cơ sở dữ liệu sẽ tuần tự hóa khi cần thiết để thực thi ràng buộc ... và nếu không, thì động cơ là vô dụng. Tất nhiên, gói điều này trong một giao dịch nối tiếp sẽ làm cho điều này chính xác hơn và ít bị ảnh hưởng bởi các bế tắc hoặc chèn không thành công.
Triynko

19
@Triynko, tôi nghĩ @Sam Saffron có nghĩa là nếu hai luồng + xen kẽ theo đúng trình tự thì máy chủ sql sẽ đưa ra lỗi cho thấy vi phạm khóa chính sẽ xảy ra. Gói nó trong một giao dịch tuần tự hóa là cách chính xác để ngăn ngừa lỗi trong tập hợp các câu lệnh trên.
EBarr

1
Ngay cả khi bạn có khóa chính là tự động tăng, thì mối quan tâm của bạn sẽ là bất kỳ ràng buộc duy nhất nào có thể có trên bảng.
Seph

1
cơ sở dữ liệu nên quan tâm đến các vấn đề chính. Những gì bạn đang nói là nếu cập nhật thất bại và quá trình khác đến đó trước tiên với một chèn của bạn sẽ thất bại. Trong trường hợp đó bạn có một điều kiện cuộc đua nào. Việc khóa sẽ không thay đổi thực tế rằng điều kiện hậu sẽ là một trong những quy trình thử viết sẽ nhận được giá trị.
CashCow

93

Nhiều người sẽ đề nghị bạn sử dụng MERGE, nhưng tôi cảnh báo bạn chống lại nó. Theo mặc định, nó không bảo vệ bạn khỏi các điều kiện đồng thời và chủng tộc hơn bất kỳ câu lệnh nào và nó đưa ra các mối nguy hiểm khác:

http://www.mssqltips.com/sqlservertip/3074/use-caestion-with-sql-servers-merge-statement/

Ngay cả với cú pháp "đơn giản hơn" này có sẵn, tôi vẫn thích cách tiếp cận này (xử lý lỗi được bỏ qua cho ngắn gọn):

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE dbo.table SET ... WHERE PK = @PK;
IF @@ROWCOUNT = 0
BEGIN
  INSERT dbo.table(PK, ...) SELECT @PK, ...;
END
COMMIT TRANSACTION;

Rất nhiều người sẽ đề xuất theo cách này:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
  UPDATE ...
END
ELSE
  INSERT ...
END
COMMIT TRANSACTION;

Nhưng tất cả những thành tựu này là đảm bảo bạn có thể cần phải đọc bảng hai lần để xác định (các) hàng cần cập nhật. Trong mẫu đầu tiên, bạn sẽ chỉ cần xác định vị trí (các) hàng một lần. (Trong cả hai trường hợp, nếu không tìm thấy hàng nào từ lần đọc đầu tiên, sẽ xảy ra hiện tượng chèn.)

Những người khác sẽ đề xuất theo cách này:

BEGIN TRY
  INSERT ...
END TRY
BEGIN CATCH
  IF ERROR_NUMBER() = 2627
    UPDATE ...
END CATCH

Tuy nhiên, đây là vấn đề nếu không vì lý do nào khác ngoài việc để SQL Server bắt ngoại lệ mà bạn có thể đã ngăn chặn ngay từ đầu đắt hơn nhiều, ngoại trừ trong trường hợp hiếm hoi khi hầu hết mọi thao tác chèn đều thất bại. Tôi chứng minh nhiều như ở đây:


3
Điều gì về việc chèn / cập nhật TỪ một bảng tem mà chèn / cập nhật nhiều bản ghi?
user960567

@ user960567 Chà,UPDATE target SET col = tmp.col FROM target INNER JOIN #tmp ON <key clause>; INSERT target(...) SELECT ... FROM #tmp AS t WHERE NOT EXISTS (SELECT 1 FROM target WHERE key = t.key);
Aaron Bertrand

4
Rất vui được trả lời sau hơn 2 năm :)
user960567

12
@ user960567 Xin lỗi, tôi không luôn luôn nhận được thông báo nhận xét trong thời gian thực.
Aaron Bertrand

60
IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

Biên tập:

Than ôi, ngay cả đối với sự bất lợi của riêng tôi, tôi phải thừa nhận các giải pháp thực hiện việc này mà không có sự lựa chọn có vẻ tốt hơn vì chúng hoàn thành nhiệm vụ với một bước ít hơn.


6
Tôi vẫn thích cái này hơn Upert có vẻ giống như lập trình theo hiệu ứng phụ, và tôi chưa bao giờ thấy chỉ số cụm nhỏ khó tìm kiếm của lựa chọn ban đầu đó để gây ra vấn đề về hiệu suất trong cơ sở dữ liệu thực.
Râu Eric Z

38

Nếu bạn muốn UPSERT nhiều hơn một bản ghi tại một thời điểm, bạn có thể sử dụng câu lệnh ANML SQL: 2003 DML MERGE.

MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])

Hãy xem Bắt chước Tuyên bố MERGE trong SQL Server 2005 .


1
Trong Oracle, đưa ra một tuyên bố MERGE tôi nghĩ rằng khóa bảng. Điều tương tự có xảy ra trong SQL * Server không?
Mike McAllister

13
MERGE dễ bị ảnh hưởng bởi các điều kiện chủng tộc (xem weblogs.sqlteam.com/dang/archive/2009/01/13/ trừ) trừ khi bạn làm cho nó giữ các khóa xác nhận. Ngoài ra, hãy xem hiệu suất của MERGE trong SQL Profiler ... tôi thấy rằng nó chậm hơn về mặt chính tả và tạo ra nhiều lượt đọc hơn các giải pháp thay thế.
EBarr

@EBarr - Cảm ơn liên kết về ổ khóa. Tôi đã cập nhật câu trả lời của tôi để bao gồm gợi ý khóa đề xuất.
Eric Weilnau


10

Mặc dù khá muộn để nhận xét về điều này, tôi muốn thêm một ví dụ đầy đủ hơn bằng cách sử dụng MERGE.

Các câu lệnh Chèn + Cập nhật như vậy thường được gọi là câu lệnh "Upsert" và có thể được triển khai bằng MERGE trong SQL Server.

Một ví dụ rất hay được đưa ra ở đây: http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-merGE.aspx

Ở trên giải thích các kịch bản khóa và đồng thời là tốt.

Tôi sẽ được trích dẫn tương tự để tham khảo:

ALTER PROCEDURE dbo.Merge_Foo2
      @ID int
AS

SET NOCOUNT, XACT_ABORT ON;

MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
      ON f.ID = new_foo.ID
WHEN MATCHED THEN
    UPDATE
            SET f.UpdateSpid = @@SPID,
            UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
    INSERT
      (
            ID,
            InsertSpid,
            InsertTime
      )
    VALUES
      (
            new_foo.ID,
            @@SPID,
            SYSDATETIME()
      );

RETURN @@ERROR;

1
Có nhiều thứ khác phải lo lắng với MERGE: mssqltips.com/sqlservertip/3074/ mẹo
Aaron Bertrand

8
/*
CREATE TABLE ApplicationsDesSocietes (
   id                   INT IDENTITY(0,1)    NOT NULL,
   applicationId        INT                  NOT NULL,
   societeId            INT                  NOT NULL,
   suppression          BIT                  NULL,
   CONSTRAINT PK_APPLICATIONSDESSOCIETES PRIMARY KEY (id)
)
GO
--*/

DECLARE @applicationId INT = 81, @societeId INT = 43, @suppression BIT = 0

MERGE dbo.ApplicationsDesSocietes WITH (HOLDLOCK) AS target
--set the SOURCE table one row
USING (VALUES (@applicationId, @societeId, @suppression))
    AS source (applicationId, societeId, suppression)
    --here goes the ON join condition
    ON target.applicationId = source.applicationId and target.societeId = source.societeId
WHEN MATCHED THEN
    UPDATE
    --place your list of SET here
    SET target.suppression = source.suppression
WHEN NOT MATCHED THEN
    --insert a new line with the SOURCE table one row
    INSERT (applicationId, societeId, suppression)
    VALUES (source.applicationId, source.societeId, source.suppression);
GO

Thay thế tên bảng và trường bằng bất cứ điều gì bạn cần. Chăm sóc các điều kiện sử dụng ON . Sau đó, đặt giá trị thích hợp (và loại) cho các biến trên dòng DECLARE.

Chúc mừng.


7

Bạn có thể sử dụng MERGEStatement, Câu lệnh này được sử dụng để chèn dữ liệu nếu không tồn tại hoặc cập nhật nếu không tồn tại.

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`

@RamenChef Tôi không hiểu. Các mệnh đề KHAI THÁC ở đâu?
likejudo

@likejudo Tôi không viết cái này; Tôi chỉ sửa đổi nó. Hỏi người dùng đã viết bài.
RamenChef

5

Nếu đi đến CẬP NHẬT if-no-rows-update thì INSERT, hãy xem xét thực hiện INSERT trước để ngăn chặn tình trạng cuộc đua (giả sử không can thiệp XÓA)

INSERT INTO MyTable (Key, FieldA)
   SELECT @Key, @FieldA
   WHERE NOT EXISTS
   (
       SELECT *
       FROM  MyTable
       WHERE Key = @Key
   )
IF @@ROWCOUNT = 0
BEGIN
   UPDATE MyTable
   SET FieldA=@FieldA
   WHERE Key=@Key
   IF @@ROWCOUNT = 0
   ... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END

Ngoài việc tránh một điều kiện cuộc đua, nếu trong hầu hết các trường hợp, bản ghi sẽ tồn tại thì điều này sẽ khiến cho INSERT bị lỗi, gây lãng phí CPU.

Sử dụng MERGE có lẽ thích hợp hơn cho SQL2008 trở đi.


Ý tưởng thú vị, nhưng cú pháp không chính xác. CHỌN cần TỪ <table_source> và TOP 1 (trừ khi bảng_source được chọn chỉ có 1 hàng).
jk7

Cảm ơn. Tôi đã thay đổi nó thành KHÔNG HIỆN TẠI. Sẽ chỉ có một hàng khớp với nhau vì bài kiểm tra "khóa" theo O / P (mặc dù đó có thể cần phải là khóa nhiều phần :))
Kristen

4

Điều đó phụ thuộc vào mô hình sử dụng. Người ta phải nhìn vào bức tranh lớn sử dụng mà không bị lạc trong các chi tiết. Ví dụ: nếu mẫu sử dụng là 99% cập nhật sau khi bản ghi được tạo, thì 'UPSERT' là giải pháp tốt nhất.

Sau lần chèn đầu tiên (nhấn), nó sẽ là tất cả các cập nhật câu lệnh đơn, không có if hoặc buts. Điều kiện 'where' trên chèn là cần thiết nếu không nó sẽ chèn trùng lặp và bạn không muốn xử lý khóa.

UPDATE <tableName> SET <field>=@field WHERE key=@key;

IF @@ROWCOUNT = 0
BEGIN
   INSERT INTO <tableName> (field)
   SELECT @field
   WHERE NOT EXISTS (select * from tableName where key = @key);
END

2

MS SQL Server 2008 giới thiệu câu lệnh MERGE mà tôi tin là một phần của tiêu chuẩn SQL: 2003. Như nhiều người đã chỉ ra rằng việc xử lý các trường hợp một hàng không phải là vấn đề lớn, nhưng khi xử lý các bộ dữ liệu lớn, người ta cần một con trỏ, với tất cả các vấn đề về hiệu năng đi kèm. Tuyên bố MERGE sẽ được hoan nghênh bổ sung khi xử lý các bộ dữ liệu lớn.


1
Tôi chưa bao giờ cần sử dụng một con trỏ để làm điều này với các bộ dữ liệu lớn. Bạn chỉ cần một bản cập nhật cập nhật các bản ghi khớp và chèn với một lựa chọn thay vì mệnh đề giá trị còn lại tham gia vào bảng.
HLGEM

1

Trước khi tất cả mọi người nhảy vào GIỎI vì sợ những người dùng láu cá này chạy trực tiếp các sprocs của bạn :-) hãy để tôi chỉ ra rằng bạn phải đảm bảo tính duy nhất của PK mới theo thiết kế (khóa nhận dạng, trình tạo chuỗi trong Oracle, các chỉ mục duy nhất cho ID bên ngoài, các truy vấn được bao phủ bởi các chỉ mục). Đó là alpha và omega của vấn đề. Nếu bạn không có điều đó, sẽ không có GIỜ nào của vũ trụ sẽ cứu bạn và nếu bạn có điều đó thì bạn không cần bất cứ điều gì ngoài UPDLOCK trong lần chọn đầu tiên (hoặc sử dụng cập nhật trước).

Sprocs thường chạy trong các điều kiện rất kiểm soát và với giả định của một người gọi đáng tin cậy (tầng giữa). Có nghĩa là nếu một mẫu upert đơn giản (cập nhật + chèn hoặc hợp nhất) từng thấy PK trùng lặp có nghĩa là một lỗi trong thiết kế bảng giữa hoặc bảng của bạn và thật tốt khi SQL sẽ phát ra lỗi trong trường hợp đó và từ chối bản ghi. Đặt một GIỜ trong trường hợp này tương đương với việc ăn ngoại lệ và lấy dữ liệu có khả năng bị lỗi, bên cạnh đó làm giảm sự hoàn hảo của bạn.

Phải nói rằng, Sử dụng MERGE hoặc CẬP NHẬT thì INSERT dễ dàng hơn trên máy chủ của bạn và ít xảy ra lỗi hơn vì bạn không phải nhớ thêm (UPDLOCK) để chọn trước. Ngoài ra, nếu bạn đang thực hiện chèn / cập nhật theo từng đợt nhỏ, bạn cần biết dữ liệu của mình để quyết định liệu giao dịch có phù hợp hay không. Nó chỉ là một tập hợp các hồ sơ không liên quan sau đó giao dịch "bao bọc" bổ sung sẽ gây bất lợi.


1
Nếu bạn chỉ thực hiện cập nhật sau đó chèn mà không có sự cô lập khóa hoặc nâng cao, thì hai người dùng có thể cố gắng truyền lại cùng một dữ liệu (Tôi sẽ không coi đó là lỗi ở tầng giữa nếu hai người dùng cố gắng gửi cùng một thông tin tại đồng thời - phụ thuộc rất nhiều vào bối cảnh, phải không?). Cả hai đều nhập bản cập nhật, trả về 0 hàng cho cả hai, sau đó cả hai đều cố gắng chèn. Một người chiến thắng, người kia được một ngoại lệ. Đây là những gì mọi người thường cố gắng tránh.
Aaron Bertrand

1

Điều kiện cuộc đua có thực sự quan trọng nếu lần đầu tiên bạn thử cập nhật sau đó là chèn? Hãy nói rằng bạn có hai luồng muốn đặt giá trị cho khóa chính :

Chủ đề 1: value = 1
Chủ đề 2: value = 2

Ví dụ kịch bản điều kiện cuộc đua

  1. khóa không được xác định
  2. Chủ đề 1 không cập nhật
  3. Chủ đề 2 không cập nhật
  4. Chính xác một trong các chủ đề 1 hoặc chủ đề 2 thành công với chèn. Ví dụ chủ đề 1
  5. Các luồng khác không thành công khi chèn (với khóa trùng lặp lỗi) - luồng 2.

    • Kết quả: "đầu tiên" của hai mặt phẳng để chèn, quyết định giá trị.
    • Kết quả mong muốn: Cuối cùng trong 2 luồng để ghi dữ liệu (cập nhật hoặc chèn) sẽ quyết định giá trị

Nhưng; trong một môi trường đa luồng, bộ lập lịch hệ điều hành quyết định thứ tự thực hiện luồng - trong kịch bản trên, nơi chúng ta có điều kiện cuộc đua này, chính hệ điều hành đã quyết định trình tự thực hiện. Tức là: Thật sai khi nói rằng "luồng 1" hoặc "luồng 2" là "đầu tiên" từ quan điểm hệ thống.

Khi thời gian thực hiện quá gần cho luồng 1 và luồng 2, kết quả của điều kiện cuộc đua không thành vấn đề. Yêu cầu duy nhất là một trong các luồng nên xác định giá trị kết quả.

Đối với việc triển khai: Nếu cập nhật theo sau là chèn kết quả trong lỗi "khóa trùng lặp", thì điều này sẽ được coi là thành công.

Ngoài ra, tất nhiên người ta không bao giờ nên cho rằng giá trị trong cơ sở dữ liệu giống với giá trị bạn đã viết cuối cùng.


1

Trong SQL Server 2008, bạn có thể sử dụng câu lệnh MERGE


11
đây là một bình luận trong trường hợp không có bất kỳ mã ví dụ thực tế nào, điều này cũng giống như nhiều bình luận khác trên trang web.
swasheck

Rất cũ, nhưng một ví dụ sẽ tốt đẹp.
Matt McCabe

0

Tôi đã thử giải pháp dưới đây và nó hoạt động với tôi, khi yêu cầu đồng thời cho câu lệnh chèn xảy ra.

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert table (key, ...)
   values (@key, ...)
end
commit tran

0

Bạn có thể sử dụng truy vấn này. Làm việc trong tất cả các phiên bản SQL Server. Thật đơn giản và rõ ràng. Nhưng bạn cần sử dụng 2 truy vấn. Bạn có thể sử dụng nếu bạn không thể sử dụng MERGE

    BEGIN TRAN

    UPDATE table
    SET Id = @ID, Description = @Description
    WHERE Id = @Id

    INSERT INTO table(Id, Description)
    SELECT @Id, @Description
    WHERE NOT EXISTS (SELECT NULL FROM table WHERE Id = @Id)

    COMMIT TRAN

LƯU Ý: Hãy giải thích câu trả lời phủ định


Tôi đoán thiếu khóa?
Zeek2

Không thiếu khóa ... Tôi sử dụng "TRẦN". Các giao dịch máy chủ sql mặc định có khóa.
Victor Sanchez

-2

Nếu bạn sử dụng ADO.NET, DataAd CHƯƠNG sẽ xử lý việc này.

Nếu bạn muốn tự xử lý, đây là cách:

Đảm bảo có một ràng buộc khóa chính trên cột khóa của bạn.

Sau đó, bạn:

  1. Làm cập nhật
  2. Nếu cập nhật thất bại vì bản ghi với khóa đã tồn tại, hãy thực hiện thao tác chèn. Nếu cập nhật không thất bại, bạn đã hoàn thành.

Bạn cũng có thể thực hiện theo cách khác, tức là thực hiện thao tác chèn trước và thực hiện cập nhật nếu quá trình chèn thất bại. Thông thường cách đầu tiên là tốt hơn, bởi vì các cập nhật được thực hiện thường xuyên hơn so với chèn.


... và thực hiện thao tác chèn trước (đôi khi biết rằng nó sẽ thất bại) rất tốn kém cho SQL Server. sqlperformance.com/2012/08/t-sql-queries/error-handling
Aaron Bertrand

-3

Thực hiện nếu tồn tại ... khác ... liên quan đến việc thực hiện tối thiểu hai yêu cầu (một để kiểm tra, một để thực hiện hành động). Cách tiếp cận sau đây chỉ yêu cầu một bản ghi tồn tại, hai nếu yêu cầu chèn:

DECLARE @RowExists bit
SET @RowExists = 0
UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123
IF @RowExists = 0
  INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx')

-3

Tôi thường làm những gì mà một số áp phích khác đã nói liên quan đến việc kiểm tra nó tồn tại trước và sau đó làm bất cứ điều gì đúng đường dẫn. Một điều bạn nên nhớ khi thực hiện điều này là kế hoạch thực hiện được lưu trữ bởi sql có thể không tối ưu cho một đường dẫn này hay đường dẫn khác. Tôi tin rằng cách tốt nhất để làm điều này là gọi hai thủ tục được lưu trữ khác nhau.

Đầu tiên:
Nếu tồn tại
   Gọi SecondSP (UpdateProc)
Khác
   Gọi ThirdSP (insertProc)

Bây giờ, tôi không làm theo lời khuyên của mình rất thường xuyên, vì vậy hãy mang nó với một hạt muối.


Điều này có thể có liên quan trong các phiên bản SQL Server cổ, nhưng các phiên bản hiện đại có trình biên dịch mức câu lệnh. Dĩa v.v ... không phải là một vấn đề và việc sử dụng các quy trình riêng cho những điều này không giải quyết được bất kỳ vấn đề nào vốn có trong việc đưa ra lựa chọn giữa cập nhật và chèn dù sao ...
Aaron Bertrand

-10

Chọn, nếu bạn nhận được kết quả, hãy cập nhật nó, nếu không, hãy tạo nó.


3
Đó là hai cuộc gọi đến cơ sở dữ liệu.
Chris Cudmore

3
Tôi không thấy vấn đề gì với điều đó.
Clint Ecker

10
Đó là hai cuộc gọi tới DB, đó là vấn đề, bạn kết thúc nhân đôi số vòng tròn cho DB. Nếu ứng dụng đạt db với nhiều lần chèn / cập nhật, nó sẽ ảnh hưởng đến hiệu suất. UPSERT là một chiến lược tốt hơn.
Kev

5
nó cũng tạo ra một điều kiện cuộc đua không?
niico
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.