IDENTITY
插入行的最佳方法是什么?
我了解@@IDENTITY
和了解IDENT_CURRENT
,SCOPE_IDENTITY
但不了解它们各自的优缺点。
有人可以解释这些差异,以及何时应使用它们?
OUTPUT
于SQL Server中的子句。
IDENTITY
插入行的最佳方法是什么?
我了解@@IDENTITY
和了解IDENT_CURRENT
,SCOPE_IDENTITY
但不了解它们各自的优缺点。
有人可以解释这些差异,以及何时应使用它们?
OUTPUT
于SQL Server中的子句。
Answers:
@@IDENTITY
返回在所有范围内为当前会话中的任何表生成的最后一个标识值。 您需要小心,因为它是跨作用域的。您可以从触发器而不是当前语句中获取值。
SCOPE_IDENTITY()
返回为当前会话和当前范围中的任何表生成的最后一个标识值。 通常您要使用什么。
IDENT_CURRENT('tableName')
返回在任何会话和任何作用域中为特定表生成的最后一个标识值。这可以让您指定要从哪个表获取值,以防上述两个表不是您真正需要的表(非常少见)。另外,正如@ Guy Starbuck提到的那样,“如果要获取未插入记录的表的当前IDENTITY值,则可以使用它。”
该OUTPUT
条款的的INSERT
声明将让您访问每一个经该语句插入行。由于它是针对特定语句的,因此它比上面的其他函数更直接。但是,它有点冗长(您需要将其插入到表变量/临时表中,然后对其进行查询),即使在语句回滚的错误情况下,它也可以提供结果。也就是说,如果您的查询使用并行执行计划,则这是获得身份的唯一保证方法(缺少关闭并行性)。但是,它在触发器之前执行,不能用于返回触发器生成的值。
output
您就不需要创建临时表来存储和查询结果。只需省略into
输出子句的一部分,它将把它们输出到结果集。
OUTPUT
只要您不使用触发器并处理错误,这就是“最佳”建议,但这SCOPE_IDENTITY
是最简单且很少
我相信检索插入的ID的最安全,最准确的方法是使用output子句。
例如(摘自以下MSDN文章)
USE AdventureWorks2008R2;
GO
DECLARE @MyTableVar table( NewScrapReasonID smallint,
Name varchar(50),
ModifiedDate datetime);
INSERT Production.ScrapReason
OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
INTO @MyTableVar
VALUES (N'Operator error', GETDATE());
--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM @MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate
FROM Production.ScrapReason;
GO
OUTPUT
,因此看起来只有SQL Server 2000和更早版本没有它
我说的是与其他人相同的话,所以每个人都是对的,我只是想让事情变得更清楚。
@@IDENTITY
返回客户端连接到数据库所插入的最后一件事的ID。
在大多数情况下,这可以正常工作,但有时会触发一个触发器,插入您不知道的新行,并且您将从新行中获取ID,而不是您想要的行
SCOPE_IDENTITY()
解决了这个问题。它返回插入到发送到数据库的SQL代码中的最后一件事的ID 。如果触发器创建了额外的行,它们将不会导致返回错误的值。万岁
IDENT_CURRENT
返回任何人插入的最后一个ID。如果某个其他应用恰巧在不幸的时间插入另一行,则您将获得该行的ID,而不是您的ID。
如果您想安全播放,请务必使用SCOPE_IDENTITY()
。如果您坚持使用,以后@@IDENTITY
又有人决定添加触发器,则所有代码都会中断。
获取新插入的行的标识的最佳(读取:最安全)方法是使用以下output
子句:
create table TableWithIdentity
( IdentityColumnName int identity(1, 1) not null primary key,
... )
-- type of this table's column must match the type of the
-- identity column of the table you'll be inserting into
declare @IdentityOutput table ( ID int )
insert TableWithIdentity
( ... )
output inserted.IdentityColumnName into @IdentityOutput
values
( ... )
select @IdentityValue = (select ID from @IdentityOutput)
scope_identity()
无论如何,单行插入(的最常见情况)很少得到并行计划。并且此错误已在此答案得到解决的一年以上。
output
而不是scope_identity()
。我在答案中删除了有关群集的FUD。
当您使用实体框架时,它在内部使用该OUTPUT
技术来返回新插入的ID值
DECLARE @generated_keys table([Id] uniqueidentifier)
INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID INTO @generated_keys
VALUES('Malleable logarithmic casing');
SELECT t.[TurboEncabulatorID ]
FROM @generated_keys AS g
JOIN dbo.TurboEncabulators AS t
ON g.Id = t.TurboEncabulatorID
WHERE @@ROWCOUNT > 0
输出结果存储在临时表变量中,再联接回表,并从表中返回行值。
注意:我不知道为什么EF会将临时表内部联接回真实表(在什么情况下两者不匹配)。
但这就是EF所做的。
此技术(OUTPUT
)仅在SQL Server 2008或更高版本上可用。
实体框架联接回原始表而不是简单地使用OUTPUT
值的原因是因为EF也使用此技术来获取rowversion
新插入的行的。
您可以使用您的实体框架模型乐观并发使用Timestamp
属性: 🕗
public class TurboEncabulator
{
public String StatorSlots)
[Timestamp]
public byte[] RowVersion { get; set; }
}
执行此操作时,Entity Framework将需要rowversion
新插入的行的:
DECLARE @generated_keys table([Id] uniqueidentifier)
INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID INTO @generated_keys
VALUES('Malleable logarithmic casing');
SELECT t.[TurboEncabulatorID], t.[RowVersion]
FROM @generated_keys AS g
JOIN dbo.TurboEncabulators AS t
ON g.Id = t.TurboEncabulatorID
WHERE @@ROWCOUNT > 0
为了检索此内容,Timetsamp
您不能使用OUTPUT
子句。
那是因为如果桌子上有一个触发器,Timestamp
那么您的任何输出都会是错误的:
如果表上有触发器,则返回的时间戳永远不会正确。所以你必须单独使用SELECT
。
即使您愿意遭受错误的行版本控制,执行另一个操作的另一个原因SELECT
是您无法rowversion
在表变量中输出a :
DECLARE @generated_keys table([Id] uniqueidentifier, [Rowversion] timestamp)
INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID, inserted.Rowversion INTO @generated_keys
VALUES('Malleable logarithmic casing');
这样做的第三个原因是对称。在UPDATE
带有触发器的表上执行时,不能使用OUTPUT
子句。尝试做UPDATE
与OUTPUT
不支持,并会给出错误:
唯一的方法是通过后续SELECT
声明:
UPDATE TurboEncabulators
SET StatorSlots = 'Lotus-O deltoid type'
WHERE ((TurboEncabulatorID = 1) AND (RowVersion = 792))
SELECT RowVersion
FROM TurboEncabulators
WHERE @@ROWCOUNT > 0 AND TurboEncabulatorID = 1
TurboEncabulators
:)
@@ IDENTITY,SCOPE_IDENTITY和IDENT_CURRENT是相似的函数,它们返回插入到表的IDENTITY列中的最后一个值。
@@ IDENTITY和SCOPE_IDENTITY将返回当前会话中任何表中生成的最后一个标识值。但是,SCOPE_IDENTITY仅在当前范围内返回该值。@@ IDENTITY不限于特定范围。
IDENT_CURRENT不受范围和会话的限制;它仅限于指定的表。IDENT_CURRENT返回为任何会话和任何作用域中的特定表生成的标识值。有关更多信息,请参见IDENT_CURRENT。
@@ IDENTITY是使用当前SQL连接插入的最后一个身份。这是从插入存储过程返回的一个好值,在该存储过程中,您只需要为新记录插入标识,而不必关心之后是否添加了更多行。
SCOPE_IDENTITY是使用当前SQL连接在当前作用域中插入的最后一个标识,也就是说,如果在插入后基于触发器插入了第二个IDENTITY,它将不会反映在SCOPE_IDENTITY中,仅反映您执行的插入。坦白说,我从来没有理由使用它。
IDENT_CURRENT(表名)是最后插入的标识,无论连接或作用域如何。如果要获取未插入记录的表的当前IDENTITY值,可以使用此方法。
我不能说其他版本的SQL Server,但在2012年,直接输出就可以了。您无需为临时表而烦恼。
INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES (...)
顺便说一句,该技术在插入多行时也适用。
INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES
(...),
(...),
(...)
输出量
ID
2
3
4
OUTPUT
。如果不需要临时表,那么查询最终会变得简单得多。
总是使用scope_identity(),就不需要其他任何东西了。
创建一个uuid
并将其插入到列中。然后,您可以轻松地使用uuid标识您的行。那是您可以实施的唯一100%有效的解决方案。所有其他解决方案都太复杂或无法在相同的情况下使用。例如:
1)创建行
INSERT INTO table (uuid, name, street, zip)
VALUES ('2f802845-447b-4caa-8783-2086a0a8d437', 'Peter', 'Mainstreet 7', '88888');
2)获取创建的行
SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';
uuid
在数据库中创建索引。因此,将更快地找到该行。
https://www.npmjs.com/package/uuid
。 const uuidv4 = require('uuid/v4'); const uuid = uuidv4()
保证所插入行的身份的另一种方法是指定身份值SET IDENTITY_INSERT ON
,然后使用and OFF
。这样可以保证您确切知道身份值是什么!只要不使用这些值,就可以将这些值插入到identity列中。
CREATE TABLE #foo
(
fooid INT IDENTITY NOT NULL,
fooname VARCHAR(20)
)
SELECT @@Identity AS [@@Identity],
Scope_identity() AS [SCOPE_IDENTITY()],
Ident_current('#Foo') AS [IDENT_CURRENT]
SET IDENTITY_INSERT #foo ON
INSERT INTO #foo
(fooid,
fooname)
VALUES (1,
'one'),
(2,
'Two')
SET IDENTITY_INSERT #foo OFF
SELECT @@Identity AS [@@Identity],
Scope_identity() AS [SCOPE_IDENTITY()],
Ident_current('#Foo') AS [IDENT_CURRENT]
INSERT INTO #foo
(fooname)
VALUES ('Three')
SELECT @@Identity AS [@@Identity],
Scope_identity() AS [SCOPE_IDENTITY()],
Ident_current('#Foo') AS [IDENT_CURRENT]
-- YOU CAN INSERT
SET IDENTITY_INSERT #foo ON
INSERT INTO #foo
(fooid,
fooname)
VALUES (10,
'Ten'),
(11,
'Eleven')
SET IDENTITY_INSERT #foo OFF
SELECT @@Identity AS [@@Identity],
Scope_identity() AS [SCOPE_IDENTITY()],
Ident_current('#Foo') AS [IDENT_CURRENT]
SELECT *
FROM #foo
如果要从另一个源加载数据或合并来自两个数据库等的数据,这可能是非常有用的技术。
即使这是一个较旧的线程,也有一种更新的方法可以避免旧版本的SQL Server中IDENTITY列的某些陷阱,例如服务器重新启动后身份值中的空白。序列在SQL Server 2016中可用,并且向前更新,这是较新的方法是使用TSQL创建SEQUENCE对象。这使您可以在SQL Server中创建自己的数字序列对象并控制其递增方式。
这是一个例子:
CREATE SEQUENCE CountBy1
START WITH 1
INCREMENT BY 1 ;
GO
然后在TSQL中,您将执行以下操作以获取下一个序列ID:
SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO
在插入语句之后,您需要添加它。并确保要在其中插入数据的表名。您将获得当前行,而不是当前受插入语句影响的行。
IDENT_CURRENT('tableName')
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...)
或更旧的方法:INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();
您可以使用ExecuteScalar()在c#中获取它。