SQL Server有三种不同的函数可以用来获得含有标识列的表里最后生成的标识值: @@IDENTITY SCOPE_IDENTITY() IDENT_CURRENT('数据表名') 以上三个函数虽然都可以返回数据库引擎最后生成插入标识列的值,但是根据插入行的来源(例如:存储过程或触发器)以及插
SQL Server有三种不同的函数可以用来获得含有标识列的表里最后生成的标识值:
以上三个函数虽然都可以返回数据库引擎最后生成插入标识列的值,但是根据插入行的来源(例如:存储过程或触发器)以及插入该行的连接不同,这三个函数在功能上也有所不同。
@@IDENTITY函数可以返回所有范围内当前连接插入最后所生成的标识值(包括任何调用的存储过程和触发器)。这个函数不止可以适用于表。函数返回的值是最后表插入行生成的标识值。
SCOPE_IDENTITY()函数跟上一个函数几乎是一摸一样的,不同的地方:即前者返回的值只限于当前范围(即执行中的存储过程)。
最后是IDENT_CURRENT函数,它可以用于所有范围和所有连接,获得最后生成的表标识值。跟前面两个函数不同的是,这个函数只用于表,并且使用[数据表名]作为一个参数。
我们可以举实例来演示上述函数是如何运作的。
首先,我们创建两个简单的例表:一个代表客户表,一个代表审计表。创建审计表的目的是为了跟踪数据库里插入和删除信息的所有记录。
以下是引用片段: CREATE TABLE dbo.customer (customerid INT IDENTITY(1,1) PRIMARY KEY) GO CREATE TABLE dbo.auditlog (auditlogid INT IDENTITY(1,1) PRIMARY KEY, customerid INT, action CHAR(1), changedate datetime DEFAULT GETDATE()) GO |
然后,我们还要创建一个存储过程和一个辅助触发器,这个存储过程将在数据库表里插入新的客户行,并返回生成的标识值,而触发器则会向审计表插入行:
以下是引用片段: CREATE TRIGGER dbo.tr_customer_log ON dbo.customer |
现在我们可以执行程序,创建客户表的第一行了:
以下是引用片段: DECLARE @customerid INT EXEC dbo.p_InsertCustomer @customerid output SELECT @customerid AS customerid |
执行后返回了我们需要的第一个客户的值,并记录了插入审计表的条目。到目前为止,数据显示没有任何问题。
假设由于先前沟通出现了偏差,一个客户服务代表现在需要从数据库里删除掉这个新增的客户。我们现在就来把新插入的客户行删除掉:
以下是引用片段: DELETE FROM dbo.customer WHERE customerid = 1 |
现在,客户工作表为空表,而审计工作表里则有两行——第一行是记录第一次插入行,第二行是记录删除客户记录。
现在我们再往数据库里增加第二个客户信息并检测一下获得的标识值:
以下是引用片段: DECLARE @customerid INT EXEC dbo.p_InsertCustomer @customerid output SELECT @customerid AS customerid |
哇!看看出现了什么情况!如果我们现在再看客户工作表,就会发现虽然创建了客户2,但是我们的程序返回的标识值为3!到底出了什么问题呢?回想一下,前面讲过@@IDENTITY函数的作用范围,它会返回主程序调用的任何存储过程或触动任何触发器最后生成的标识值,取决于哪一个在函数被调用前最后生成标识值。在我们的例子里,初始范围是p_InsertCustomer,然后是触发器用来记录插入条目的tr_customer_log。因此我们返回获得的标识值是审计工作表里触发器插入生成的标识值,而不是我们想要的客户工作表里的生成的标识值。
在SQL Server 2000之前的版本,@@IDENTITY函数是获得标识值的唯一方法。由于会出现这样的存储过程/触发器问题,SQL Server开发团队在SQL Server 2000中引入了 SCOPE_IDENTITY()和IDENT_CURRENT这两个函数来解决这个问题。所以在旧的SQL Server版本里,要解决这个问题比较麻烦。如果是SQL Server6.5版本,我建议可以去掉标识列,然后创建一个可以包含下一个需要使用的值的辅助表,可以达到标识列的作用效果。不过这个办法也不是什么高明的办法。
现在我们来修改一下存储过程来使用SCOPE_IDENTITY()函数,并重新执行程序来添加第三个客户条目:
以下是引用片段: ALTER PROCEDURE dbo.p_InsertCustomer @customerid INT output AS SET nocount ON INSERT INTO dbo.customer DEFAULT VALUES SELECT @customerid = SCOPE_IDENTITY() GO DECLARE @customerid INT EXEC dbo.p_InsertCustomer @customerid output SELECT @customerid AS customerid |
我们返回的标识值还是3,不过这次我们获得的标识值是正确的,因为我们添加了第三个客户条目。如果我们检查一下审计工作表,就会发现里面已经有第四个条目记录新插入的客户记录。由于函数SCOPE_IDENTITY()只作用于当前范围,只返回当前执行程序的值,这样就避免了发生刚才那样的问题。
前面讲过,函数@@IDENTITY和函数SCOPE_IDENTITY()不止用于表,不像函数IDENT_CURRENT那样可以用表作为参数。使用@@IDENTITY和SCOPE_IDENTITY()这两个函数的话在设置代码时需要加倍小心,才能够从所需要的表里获得正确的标识值。从表面上来看,放弃这两个函数,只使用函数IDENT_CURRENT并指定表是更安全的办法。这样可以避免出现获得错误标识值的情况,对吧?记得先前说过函数IDENT_CURRENT不仅会跨范围,而且它还会跨连接。也就是说,使用这个函数生成的值不仅仅限于你的连接所执行的程序,它的涵盖范围还包括整个数据库所有的连接。因此,即使是在规模较小的OLTP环境里,它也会出现不能准确返回所需值的问题。这样就可能发生类似前面@@IDENTITY函数/触发器的数据损坏问题。
我的建议是函数SCOPE_IDENTITY()是三个函数里最安全的函数,应该设置为默认函数。使用这个函数,你可以放心地添加触发器和次存储过程,无需担心意外损坏数据。而另外两个函数可以保留应付特殊的情况,当遇到需要使用这两个函数的特殊情况时,建议记录它们的使用情况并进行测试。
小技巧:
If Exists(Select * from SysColumns Where ID=OBJECT_ID(N'TEST1') And COLUMNPROPERTY(ID,Name,'IsIdentity')=1) Print N'有自增列' Else Print N'没有自增列' |
Select b.name,a.* from SysColumns a,sysobjects b Where a.id=b.id and COLUMNPROPERTY(a.ID,a.Name,'IsIdentity')=1 |