如何在 SQL Server 2005 中进行更新插入(更新或插入)
Posted
技术标签:
【中文标题】如何在 SQL Server 2005 中进行更新插入(更新或插入)【英文标题】:How to upsert (update or insert) in SQL Server 2005 【发布时间】:2012-06-16 03:40:14 【问题描述】:我有一个表,我在其中为员工插入行,但是下次当我想插入行时,我不想再次为该员工插入数据,只想更新所需的列,如果它退出那里,如果没有然后创建新行
我们如何在 SQL Server 2005 中做到这一点?
我正在使用jsp
我的查询是
String sql="insert into table1(id,name,itemname,itemcatName,itemQty)values('val1','val2','val3','val4','val5')";
如果是第一次,则将其插入数据库,否则如果存在则更新它
怎么办?
【问题讨论】:
如何使用 MERGE 子句 以及包含用户详细信息的临时表?这不是更好吗? Sakhile -- 是的 -- 那么你在... answer.. 部分的答案在哪里?MERGE
适合这种用例,但需要注意的是,它仅在 SQL Server 2008 中引入(推测 OP 在六年后仍未使用 2005)。
【参考方案1】:
尝试检查是否存在:
IF NOT EXISTS (SELECT * FROM dbo.Employee WHERE ID = @SomeID)
INSERT INTO dbo.Employee(Col1, ..., ColN)
VALUES(Val1, .., ValN)
ELSE
UPDATE dbo.Employee
SET Col1 = Val1, Col2 = Val2, ...., ColN = ValN
WHERE ID = @SomeID
您可以轻松地将其包装到一个存储过程中,然后从外部调用该存储过程(例如,从 C# 之类的编程语言或您使用的任何语言)。
更新:您可以将整个语句写在一个长字符串中(可行 - 但不是非常有用) - 或者您可以将其包装到存储过程中:
CREATE PROCEDURE dbo.InsertOrUpdateEmployee
@ID INT,
@Name VARCHAR(50),
@ItemName VARCHAR(50),
@ItemCatName VARCHAR(50),
@ItemQty DECIMAL(15,2)
AS BEGIN
IF NOT EXISTS (SELECT * FROM dbo.Table1 WHERE ID = @ID)
INSERT INTO dbo.Table1(ID, Name, ItemName, ItemCatName, ItemQty)
VALUES(@ID, @Name, @ItemName, @ItemCatName, @ItemQty)
ELSE
UPDATE dbo.Table1
SET Name = @Name,
ItemName = @ItemName,
ItemCatName = @ItemCatName,
ItemQty = @ItemQty
WHERE ID = @ID
END
然后从您的 ADO.NET 代码中调用该存储过程
【讨论】:
在哪里写上面的代码在上面的sql字符串中或者需要创建触发器或过程。 用单个字符串写入它看起来像 String sql="IF NOT EXIST (SELECT*FROM Employee WHERE ID=@someid)INSERT INTO EMPLOYEE (col1,...,colN)values(val1, ..,valN)ELSE UPDATE EMPLOYEE SET col1=val1,col2=val2,...,colN=valN WHERE ID=@someID" 可以吗? @ybc126:如果您的 SQL 中有必要的空格 - 是的。但是这样的事情绝对是不可取的!将其放入存储过程(或使用 ORM 来处理)会更好 很多时候建议不要使用Select * from
。我不知道使用IF NOT EXISTS (SELECT * FROM ...
时是否重要,但我可能会使用IF NOT EXISTS (SELECT Id FROM ...
只是为了避免使用*
。
我认为存储过程不会处理并发请求是正确的。如果两个请求同时访问试图插入相同的主键,而主键在表中还不存在,那么两个请求将同时通过第一个条件(因为 select 可以并行运行)。但是,INSERT 需要在排除中运行,因此其中一个查询将添加新记录,而另一个查询将出现异常,表明存在重复的主键。【参考方案2】:
您可以使用@@ROWCOUNT
来检查是否应该插入或更新行:
update table1
set name = 'val2', itemname = 'val3', itemcatName = 'val4', itemQty = 'val5'
where id = 'val1'
if @@ROWCOUNT = 0
insert into table1(id, name, itemname, itemcatName, itemQty)
values('val1', 'val2', 'val3', 'val4', 'val5')
在这种情况下,如果更新失败,将插入新行
【讨论】:
这应该被接受,因为它是最有效的。它不会搜索两次更新【参考方案3】:您可以检查该行是否存在,然后插入或更新,但这保证您将执行两个 SQL 操作而不是一个:
-
检查行是否存在
插入或更新行
更好的解决方案是始终先更新,如果没有更新任何行,则执行 INSERT,如下所示:
update table1
set name = 'val2', itemname = 'val3', itemcatName = 'val4', itemQty = 'val5'
where id = 'val1'
if @@ROWCOUNT = 0
insert into table1(id, name, itemname, itemcatName, itemQty)
values('val1', 'val2', 'val3', 'val4', 'val5')
这将需要一个 SQL 操作或两个 SQL 操作,具体取决于该行是否已存在。
但如果性能确实是一个问题,那么您需要弄清楚这些操作是否更有可能是 INSERT 或 UPDATE。如果更新更常见,请执行上述操作。如果 INSERT 更常见,您可以反过来执行此操作,但您必须添加错误处理。
BEGIN TRY
insert into table1(id, name, itemname, itemcatName, itemQty)
values('val1', 'val2', 'val3', 'val4', 'val5')
END TRY
BEGIN CATCH
update table1
set name = 'val2', itemname = 'val3', itemcatName = 'val4', itemQty = 'val5'
where id = 'val1'
END CATCH
要确定是否需要执行 UPDATE 或 INSERT,您必须在单个 TRANSACTION 中执行两个操作。理论上,在第一个 UPDATE 或 INSERT(甚至是 EXISTS 检查)之后,但在下一个 INSERT/UPDATE 语句之前,数据库可能已经更改,导致第二个语句无论如何都会失败。这是极其罕见的,而且交易的开销可能不值得。
或者,您可以使用称为 MERGE 的单个 SQL 操作来执行 INSERT 或 UPDATE,但对于这种单行操作来说,这也可能是多余的。
考虑阅读SQL transaction statements、race conditions、SQL MERGE statement。
【讨论】:
您提到的问题的链接是特定于 mysql 的,因为该线程是关于没有 ON DUPLICATE KEY UPDATE 选项的 SQL Server。您能否澄清您的意思是哪个答案,或者这是您的错误?【参考方案4】:这是 Michael J. Swart 撰写的一篇关于此问题的有用文章,其中涵盖了在 SQL Server 中实现 UPSERT
的不同模式和反模式:https://michaeljswart.com/2017/07/sql-server-upsert-patterns-and-antipatterns/
它解决了相关的并发问题(主键违规、死锁)——此处提供的所有答案都被视为文章中的反模式(除了使用触发器的@Bridge 解决方案,此处未涉及)。
以下是文章摘录以及作者首选的解决方案:
在带有锁提示的可序列化事务中:
CREATE PROCEDURE s_AccountDetails_Upsert ( @Email nvarchar(4000), @Etc nvarchar(max) )
AS
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRAN
IF EXISTS ( SELECT * FROM dbo.AccountDetails WITH (UPDLOCK) WHERE Email = @Email )
UPDATE dbo.AccountDetails
SET Etc = @Etc
WHERE Email = @Email;
ELSE
INSERT dbo.AccountDetails ( Email, Etc )
VALUES ( @Email, @Etc );
COMMIT
*** 上也有相关问题的答案:Insert Update stored proc on SQL Server
【讨论】:
【参考方案5】:您可以使用表上的INSTEAD OF INSERT
触发器来执行此操作,该触发器检查行的存在,然后根据它是否已经存在进行更新/插入。在 MSDN here 上有一个如何为 SQL Server 2000+ 执行此操作的示例:
CREATE TRIGGER IO_Trig_INS_Employee ON Employee
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
-- Check for duplicate Person. If no duplicate, do an insert.
IF (NOT EXISTS (SELECT P.SSN
FROM Person P, inserted I
WHERE P.SSN = I.SSN))
INSERT INTO Person
SELECT SSN,Name,Address,Birthdate
FROM inserted
ELSE
-- Log attempt to insert duplicate Person row in PersonDuplicates table.
INSERT INTO PersonDuplicates
SELECT SSN,Name,Address,Birthdate,SUSER_SNAME(),GETDATE()
FROM inserted
-- Check for duplicate Employee. If no duplicate, do an insert.
IF (NOT EXISTS (SELECT E.SSN
FROM EmployeeTable E, inserted
WHERE E.SSN = inserted.SSN))
INSERT INTO EmployeeTable
SELECT EmployeeID,SSN, Department, Salary
FROM inserted
ELSE
--If duplicate, change to UPDATE so that there will not
--be a duplicate key violation error.
UPDATE EmployeeTable
SET EmployeeID = I.EmployeeID,
Department = I.Department,
Salary = I.Salary
FROM EmployeeTable E, inserted I
WHERE E.SSN = I.SSN
END
【讨论】:
以上是关于如何在 SQL Server 2005 中进行更新插入(更新或插入)的主要内容,如果未能解决你的问题,请参考以下文章
如果存储过程在 SQL Server 2005 上的选择之前进行更新,则没有结果集
如何在 SQL Server 2005 中回滚 UPDATE 查询?
在 SQL Server 2005 的查询中与 T-SQL 进行数据比较