zoukankan      html  css  js  c++  java
  • 10. IDENTITY属性使用小结

    从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函数就更显得不好用了。

  • 相关阅读:
    Ubuntu 18.04.2 LTS美化方案
    Ubuntu 16.04升级18.04
    Spark性能优化指南——高级篇
    Spark性能优化指南——基础篇
    遗传算法(Genetic Algorithm)——基于Java实现
    Linux sar命令参数详解
    Gdb调试多进程程序
    P8.打印整数
    Algorithm Book Index
    Debugging with GDB (8) 4.10 Debugging Programs with Multiple Threads
  • 原文地址:https://www.cnblogs.com/seusoftware/p/3804333.html
Copyright © 2011-2022 走看看