從SQL Server 2012開始有了Sequence,簡單用列如下:
CREATE SEQUENCE TestSeq START WITH 1 INCREMENT BY 1 ; SELECT NEXT VALUE FOR TestSeq AS NextValue;
在這之前,表中生成序列號大多都是借助IDENTITY列屬性,當然也有一些時候,是在自定義表中,自己維護序列號。
一. 創建IDENTITY列
if OBJECT_ID('test','U') is not null drop table test GO create table test(id int identity, c1 char(1)) insert test values('a'); insert test values('b'); select * from test
1. 沒有指定IDENTITY(seed ,increment),默認就是 IDENTITY(1, 1),效果同如下語句
create table test(id int identity(1,1), c1 char(1))
2. 通過函數或者系統視圖,都可以查看是否為IDENTITY列
SELECT COLUMNPROPERTY(OBJECT_ID('test'),'id','IsIdentity') AS is_identity select object_name(object_id) as table_name, is_identity,* from sys.columns where object_id=object_id('test') --and is_identity=1
3. 重置IDENTITY列的初始值,通常在數據刪除/歸檔后進行
DELETE test --delete后不會清除identity取值 DBCC CHECKIDENT('test', RESEED, 0) --RESEED會+1,所以給0,才會從1開始 DBCC CHECKIDENT('test', NORESEED) --查看當前identity取值,和表中的最大identity值 --Checking identity information: current identity value '2', current column value '3'. select * from test /* id c1 2 a 3 b 1 a 2 b */ TRUNCATE TABLE test --trucnate會清除identity取值 DBCC CHECKIDENT('test', NORESEED) --Checking identity information: current identity value 'NULL', current column value 'NULL'.
二. 獲取IDENTITY列值
插入了數據,有時還需要獲取剛才生成的序列值另作他用,返回給前端也好,或者插入其他將來需要關聯的表。
記得曾經有個面試題:假設當前表IDENTITY列最大值為N,在存儲過程中,對這個表插入1行數據,獲取到的IDENTITY列值有時小於或者大於N+1,可能是什么原因?
獲取IDENTITY列值有三種方式:
(1) IDENT_CURRENT( 'table_name' ) 返回為任何會話和任何作用域中的特定表最后生成的標識值。
(2) @@IDENTITY 返回為當前會話的所有作用域中的任何表最后生成的標識值。
(3) SCOPE_IDENTITY() 返回為當前會話和當前作用域中的任何表最后生成的標識值。
IDENT_CURRENT( 'table_name' ) 針對特定表,是全局的。@@IDENTITY和SCOPE_IDENTITY()針對所有表,區別在於作用域,也就是上下文:
(1) 如果當前INSERT語句上有函數,觸發器等(不同作用域的)對象返回的IDENTITY值,那么@@IDENTITY會取所有表上的最后1個,而不是當前表上的;
(2) SCOPE_IDENTITY()會取當前作用域所有表上最后1個IDENTITY值,被調用的函數,觸發器已經超出了作用域/上下文。所以在使用INSERT后,接着使用SCOPE_IDENTITY()獲取IDENTITY列值,就不會有問題了:
insert test values('z'); select SCOPE_IDENTITY() as curr_value
一個GO語句/批處理,也是一個上下文的分界點,但是SQL語句是順序執行的,所以一個會話里,只要在INSERT之后用SCOPE_IDENTITY()來獲取IDENTITY值是沒問題的。
三. 修改IDENTITY列值/屬性
1. 對已存在的列增加/刪除IDENTITY屬性
if OBJECT_ID('t_id') is not null drop table t_id GO create table t_id(id int,c1 char(1)) insert into t_id select 1,'a' union all select 2,'b' alter table t_id alter column id int identity(1,2) /* Msg 156, Level 15, State 1, Line 2 Incorrect syntax near the keyword 'identity'. */
直接修改列屬性會報錯,IDENTITY屬性只能伴隨着列增加/刪除。
(1) 利用中間表
在SSMS界面上設計表(SSMS/Tables/Design),可以直接增加/刪除列上的IDENTITY屬性,如果生成腳本看看的話(右擊編輯框/工具欄/菜單欄),可以發現SSMS是利用了中間表,並非在原表直接修改屬性。
表上有約束,索引等對象時,腳本會更加繁雜些。示例如下圖:
如果出現如下錯誤:
Saving changes is not permitted. The changes that you have made require the following tables to be dropped and re-created. You have either made changes to a table that can't be re-created or enabled the option Prevent saving changes that require the table to be re-created.
是因為SSMS里有個選項沒設置,SQL Server認為有刪除/重建表的腳本不安全,所以默認關閉了,需要手動開啟一下,去掉那個勾:
對表上已存在列添加IDENTITY屬性,生成的腳本如下:
BEGIN TRANSACTION SET QUOTED_IDENTIFIER ON SET ARITHABORT ON SET NUMERIC_ROUNDABORT OFF SET CONCAT_NULL_YIELDS_NULL ON SET ANSI_NULLS ON SET ANSI_PADDING ON SET ANSI_WARNINGS ON COMMIT BEGIN TRANSACTION GO CREATE TABLE dbo.Tmp_t_id ( id int NOT NULL IDENTITY (1, 1), c1 char(1) NULL ) ON [PRIMARY] GO ALTER TABLE dbo.Tmp_t_id SET (LOCK_ESCALATION = TABLE) GO SET IDENTITY_INSERT dbo.Tmp_t_id ON GO IF EXISTS(SELECT * FROM dbo.t_id) EXEC('INSERT INTO dbo.Tmp_t_id (id, c1) SELECT id, c1 FROM dbo.t_id WITH (HOLDLOCK TABLOCKX)') GO SET IDENTITY_INSERT dbo.Tmp_t_id OFF GO DROP TABLE dbo.t_id GO EXECUTE sp_rename N'dbo.Tmp_t_id', N't_id', 'OBJECT' GO COMMIT
對表上已存在列刪除IDENTITY屬性,生成的腳本如下:
BEGIN TRANSACTION SET QUOTED_IDENTIFIER ON SET ARITHABORT ON SET NUMERIC_ROUNDABORT OFF SET CONCAT_NULL_YIELDS_NULL ON SET ANSI_NULLS ON SET ANSI_PADDING ON SET ANSI_WARNINGS ON COMMIT BEGIN TRANSACTION GO CREATE TABLE dbo.Tmp_t_id ( id int NOT NULL, c1 char(1) NULL ) ON [PRIMARY] GO ALTER TABLE dbo.Tmp_t_id SET (LOCK_ESCALATION = TABLE) GO IF EXISTS(SELECT * FROM dbo.t_id) EXEC('INSERT INTO dbo.Tmp_t_id (id, c1) SELECT id, c1 FROM dbo.t_id WITH (HOLDLOCK TABLOCKX)') GO DROP TABLE dbo.t_id GO EXECUTE sp_rename N'dbo.Tmp_t_id', N't_id', 'OBJECT' GO COMMIT
(2) 利用中間列
對表上已存在列刪除IDENTITY屬性
if OBJECT_ID('t_id') is not null drop table t_id GO create table t_id(id int identity(1,1),c1 char(1)) insert into t_id select 'a' union all select 'b' select * from t_id SELECT COLUMNPROPERTY(OBJECT_ID('t_id'),'id','IsIdentity') --在表上新增一個列,把IDENTITY列值復制過去 alter table t_id add id_new int GO update t_id set id_new = id --刪除原來的列,並重命名新增列 alter table t_id drop column id exec sp_rename 't_id.id_new','id' select * from t_id SELECT COLUMNPROPERTY(OBJECT_ID('t_id'),'id','IsIdentity')
對表上已存在列添加IDENTITY屬性,用中間列的方式不太可行,因為IDENTITY列不接受UPDATE,新增的IDENTITY列無法直接復制原id的值,還得借助中間表,但如果不需要原來id的值,那么可以:
if OBJECT_ID('t_id') is not null drop table t_id GO create table t_id(id int,c1 char(1)) insert into t_id select 1,'a' union all select 3,'b' select * from t_id SELECT COLUMNPROPERTY(OBJECT_ID('t_id'),'id','IsIdentity') --在表上新增一個IDENTITY列,不復制原來的ID值 alter table t_id add id_new int identity(1,1) not null --刪除原來的列,並重命名新增列 alter table t_id drop column id exec sp_rename 't_id.id_new','id' select * from t_id SELECT COLUMNPROPERTY(OBJECT_ID('t_id'),'id','IsIdentity')
2. 在IDENTITY列上做增刪改操作(DML)
(1) 刪除操作沒有問題,直接DELETE即可
delete test where id = 2
(2) 如果要顯式INSERT某個值,需要開啟IDENTITY_INSERT這個SESSION級的選項
--注意表是否在當前數據庫 use testing_test go if OBJECT_ID('test_insert','U') is not null drop table test_insert create table test_insert(id int identity(1,1), c1 int) use master SET IDENTITY_INSERT test_insert ON /* Msg 1088, Level 16, State 11, Line 2 Cannot find the object "test_insert" because it does not exist or you do not have permissions. */ insert into testing_test.dbo.test_insert(c1) values(100) select * from testing_test.dbo.test_insert /* id c1 1 100 */ SET IDENTITY_INSERT testing_test..test_insert ON --可以跨數據庫指定表名 insert into testing_test.dbo.test_insert(id,c1) values(100,100) select * from testing_test.dbo.test_insert /* id c1 1 100 100 100 */ SET IDENTITY_INSERT testing_test..test_insert OFF insert into testing_test.dbo.test_insert(c1) values(100) --開啟IDENTITY_INSERT后會reseed identity值 select * from testing_test.dbo.test_insert /* id c1 1 100 100 100 101 100 */
(3) 如果要UPDATE IDENTITY列值,無論是否開啟IDENTITY_INSERT這個選項都無法更新
set IDENTITY_INSERT test on; update test set id = 10 where id = 1 set IDENTITY_INSERT test off; /* Msg 8102, Level 16, State 1, Line 1 Cannot update identity column 'id'. */
非要修改的話,就得借助中間表,在不含IDENTITY屬性的中間表里做完UPDATE,然后再把數據導回來。中間表可參考上面的腳本。
3. IDENTITY列屬性復制
(1) 直接從單表SELECT INTO table_name,原表其他約束,索引等等都不會被復制,但是IDENTITY屬性會被復制。
select * into test2 from test select * from test2 select columnproperty(OBJECT_ID('test'),'id','IsIdentity') select columnproperty(OBJECT_ID('test2'),'id','IsIdentity')
(2) 如果有IDENTITY屬性的表和其他表JOIN,那么IDENTITY屬性不會被復制。
select a.* into test3 from test a inner join sys.objects b on a.id = b.object_id select * from test3 select columnproperty(OBJECT_ID('test3'),'id','IsIdentity')
假如復制表時,不想要IDENTITY屬性,正好可以利用一下這個特點,如下:
select a.* into test4 from test a inner join sys.objects b on 1=2
(3) 如果用SELECT INTO table_name導數據時,FROM子句有多表關聯,且想要保留IDENTITY屬性,這時可以用INSERT,並考慮使用TABLOCK提示
if OBJECT_ID('test5','U') is not null drop table test5 GO create table test5(id int identity, c1 char(1)) select * from test5 GO set IDENTITY_INSERT test5 on; insert into test5 WITH(TABLOCK) (id,c1) select a.* from test a inner join test2 b on a.id = b.id set IDENTITY_INSERT test5 off; select * from test5 select columnproperty(OBJECT_ID('test5'),'id','IsIdentity')
這里使用了WITH(TABLOCK)選項,在SIMPLE或者BULK_LOGGED恢復模式下,SELECT…INTO table_name和INSERT INTO table_name WITH(TABLOCK)都能最小化日志。
4. 借助SWITCH來處理IDENTITY屬性,推薦
同樣也是利用中間表,上面的幾個列子都使用了INSERT,這里使用SWITCH,不再有數據倒來倒去的開銷,需要SQL Server 2008及以上版本,能比較有效地同時解決上面的3個問題:
(1) 不能直接對表上現有列增加/刪除IDENTITY屬性;
(2) 不能直接更新IDENTITY列;
(3) 復制表時,有選擇的復制IDENTITY列屬性(多表關聯,對關聯后的表做SWITCH以實現);
CREATE TABLE Temp1 ( ID INT IDENTITY(1,1) PRIMARY KEY, X VARCHAR(10) ) INSERT INTO Temp1 OUTPUT INSERTED.* SELECT 'Foo' UNION ALL SELECT 'Bar' UNION ALL SELECT 'Baz' CREATE TABLE Temp2 ( ID INT PRIMARY KEY, X VARCHAR(10) ) ALTER TABLE Temp1 SWITCH TO Temp2; SELECT COLUMNPROPERTY(OBJECT_ID('Temp1'),'id','IsIdentity') SELECT COLUMNPROPERTY(OBJECT_ID('Temp2'),'id','IsIdentity') INSERT INTO Temp2 OUTPUT INSERTED.* SELECT 10,'Foo' UNION ALL SELECT 20,'Bar' UNION ALL SELECT 5, 'Baz' UPDATE Temp2 SET ID = ID + 1; ALTER TABLE Temp2 SWITCH TO Temp1; SELECT * FROM Temp2 SELECT * FROM Temp1
另外,從SQL Server 2012開始,如果開發時使用了SEQUENCE,這些IDENTITY列的限制就都不會存在了。
四. IDENTITY函數
這是一個函數,使用時和IDENTITY屬性的格式很相似,不過兩者沒什么關系,純粹因為名字相同,順便提一下。
select IDENTITY(int,1,1) as id into #t from sysobjects select cast(IDENTITY(int,1,1) as varchar(1000)) as id into #t2 from sysobjects -- can not use expression with identity function directly
IDENTITY函數限制比較多,只能用在SELECT INTO語句里,不能結合表達式使用,而且有了ROW_NUMBER(),IDENTITY函數就更顯得不好用了。