Answers:
我理解您的问题的方式是,您有一个现有的表,该表的列到现在为止都已填充了手动值,现在您想要(1)将该列设置为IDENTITY
列,并且(2)确保IDENTITY
开始从现有行中的最新值开始。
首先,要使用一些测试数据:
CREATE TABLE dbo.ident_test (
id int NOT NULL,
xyz varchar(10) NOT NULL,
CONSTRAINT PK_ident_test PRIMARY KEY CLUSTERED (id)
);
INSERT INTO dbo.ident_test (id, xyz)
VALUES (1, 'test'),
(2, 'test'),
(5, 'test'),
(6, 'test'),
(10, 'test'),
(18, 'test'),
(19, 'test'),
(20, 'test');
目标是创建表的主键列,id
该IDENTITY
列将从要插入的下一条记录的21开始。对于此示例,该列xyz
代表表的所有其他列。
在执行任何操作之前,请阅读这篇文章底部的警告。
首先,万一出问题了:
BEGIN TRANSACTION;
现在,让我们添加一个临时工作列,id_temp
并将该列设置为现有id
列的值:
ALTER TABLE dbo.ident_test ADD id_temp int NULL;
UPDATE dbo.ident_test SET id_temp=id;
接下来,我们需要删除现有id
列(您不能仅将an添加IDENTITY
到现有列中,而必须将其创建为IDENTITY
)。主键也必须走,因为列取决于它。
ALTER TABLE dbo.ident_test DROP CONSTRAINT PK_ident_test;
ALTER TABLE dbo.ident_test DROP COLUMN id;
...,然后再次将列添加为IDENTITY
,以及主键:
ALTER TABLE dbo.ident_test ADD id int IDENTITY(1, 1) NOT NULL;
ALTER TABLE dbo.ident_test ADD CONSTRAINT PK_ident_test PRIMARY KEY CLUSTERED (id);
这就是有趣的地方。您可以IDENTITY_INSERT
在表上启用它,这意味着您可以IDENTITY
在插入新行时手动定义列的值(但是,不更新现有行)。
SET IDENTITY_INSERT dbo.ident_test ON;
有了该设置,DELETE
表中的所有行(但您要删除的行)都将OUTPUT
直接放入同一张表中-但具有特定的id
列值(来自备份列)。
DELETE FROM dbo.ident_test
OUTPUT deleted.id_temp AS id, deleted.xyz
INTO dbo.ident_test (id, xyz);
完成IDENTITY_INSERT
后,再次关闭电源。
SET IDENTITY_INSERT dbo.ident_test OFF;
删除我们添加的临时列:
ALTER TABLE dbo.ident_test DROP COLUMN id_temp;
最后,重新设置IDENTITY
列的种子,以便id
在该id
列中现有的最高编号之后恢复下一条记录:
DECLARE @maxid int;
SELECT @maxid=MAX(id) FROM dbo.ident_test;
DBCC CHECKIDENT ("dbo.ident_test", RESEED, @maxid)
查看示例表,最大id
数量为20。
SELECT * FROM dbo.ident_test;
添加另一行并检查其新内容IDENTITY
:
INSERT INTO dbo.ident_test (xyz) VALUES ('New row');
SELECT * FROM dbo.ident_test;
在示例中,新行将具有id=21
。最后,如果您满意,请提交交易:
COMMIT TRANSACTION;
重要
这不是一件微不足道的操作,它会带来很多您应注意的风险。
在专用的测试环境中执行此操作。有备份。:)
我喜欢使用BEGIN/COMMIT TRANSACTION
它,因为它可以防止其他进程在更改表时使表混乱,并且如果出现问题,它可以回滚所有内容。但是,任何其他在提交事务之前尝试访问表的过程最终都将等待。如果您有一张大桌子和/或您在生产环境中,这可能非常糟糕。
OUTPUT .. INTO
如果您的目标表具有外键约束或我不记得的许多其他功能中的任何一项,将无法使用。您可以改为将数据卸载到临时表中,然后再将其重新插入到原始表中。您也许可以使用分区切换(即使您不使用分区)。
而不是批处理或在存储过程中一个一个地运行这些语句。
尝试考虑可能取决于id
您要删除和重新创建的列的其他事项。任何索引都必须删除并重新创建(就像我们对主键所做的那样)。请记住为每个需要重新创建的索引和约束编写脚本。
禁用任何INSERT
和DELETE
触发器在桌子上。
如果重新创建表是一种选择:
如果重新创建表是您的选择,那么一切都会简单得多:
id
列为IDENTITY
,IDENTITY_INSERT ON
为表,IDENTITY_INSERT OFF
和IDENTITY_INSERT ON
,填充和禁用它。那就是我想做的,但是不知道MSSQL支持它。
使用UPDATE,DELETE或INSERT移动数据可能会花费大量时间,并且会在数据和日志文件/磁盘上使用资源(IO)。在处理大表时,可以避免用大量的记录填充事务日志:使用分区切换,仅更改元数据。
由于不涉及数据移动,因此可以真正快速地执行(几乎是瞬时的)。
问题不显示原始表DDL。以下DDL将用作此答案的示例:
CREATE TABLE dbo.idT(
id int not null
, uid uniqueidentifier not null
, name varchar(50)
);
ALTER TABLE dbo.idT ADD CONSTRAINT PK_idT PRIMARY KEY CLUSTERED(id);
此查询中添加了从0到15的六个虚拟随机ID:
WITH ids(n) AS(
SELECT x1.n+x2.n*4
FROM (values(0), (3)) as x1(n)
CROSS JOIN (values(0), (2), (3)) as x2(n)
)
INSERT INTO idt(id, uid, name)
SELECT n, NEWID(), NEWID()
FROM ids
IdT
id uid name
0 65533096-5007-43EA-88AD-D6776B3B94FA 6A69D4F2-D682-4168-A92F-4CD2E2DBC21D
3 CE87F1ED-BE1A-4F2D-8D62-E1ECA822D35B AF0524D9-0DBB-41E1-883B-003CB4E4F012
8 34A1DBFD-4F92-4F34-9F04-4CDC824AB15A 02B4BDA4-D515-4262-9031-0BE496AC24CE
11 51606C95-9DE8-4C30-B23B-F915EEA41156 93258103-9C22-4F9C-85CF-712ED0FB3CE6
12 CEC80431-0513-4751-A250-0EB3390DACAB 2DA6B8AF-3EBC-42B3-A76C-028716E24661
15 5037EA83-286F-4EBC-AD7C-E237B570C1FF 095E51E9-8C38-4104-858F-D14AA810A550
IDENTITY(0, 1)
唯一的问题idT
是IDENTITY(0, 1)
id上缺少属性。IDENTITY(0, 1)
创建具有相似结构的新表,并且:
CREATE TABLE dbo.idT_Switch(
id int identity(0, 1) not null
, uid uniqueidentifier not null
, name varchar(50)
);
ALTER TABLE dbo.idT_Switch ADD CONSTRAINT PK_idT_Switch PRIMARY KEY CLUSTERED(id);
除了IDENTITY(0, 1)
,idT_Switch
等同于idT
。
idT
必须删除外键,才能使用此技术。
在idT
与idT_Switch
表具有兼容的结构。而不是使用的DELETE
,UPDATE
并INSERT
声明从移动行idT
到idT_Switch
或idT
本身,ALTER TABLE ... SWITCH
可以使用:
ALTER TABLE dbo.idT
SWITCH TO dbo.idT_Switch;
PK_idT
(整个表)的单个“分区” 被移至PK_idT_Switch
(反之亦然)。idT
现在包含0行并idT_Switch
包含6行。
您可以在此处找到源和目标兼容性要求的完整列表:
请注意,这种使用SWITCH
不需要Enterprise Edition,因为没有显式分区。从SQL Server 2005开始,未分区表被视为具有单个分区的表。
idT
idT
现在是空无用的,可以删除:
DROP TABLE idT;
idT_Switch
可以重命名并将替换旧idT
表:
EXECUTE sys.sp_rename
@objname = N'dbo.idT_Switch',
@newname = N'idT', -- note lack of schema prefix
@objtype = 'OBJECT';
外键可以再次添加到新idT
表中。以前idT
为了使表兼容以进行切换而从中删除的所有其他内容也将需要重做。
SELECT IDENT_CURRENT( 'dbo.idT');
此命令返回0。表idT包含6行,其中MAX(id)=15。可以使用DBCC CHECKIDENT(table_name):
DBCC CHECKIDENT ('dbo.idT');
因为15大于0,所以它将自动重新设定种子,而无需寻找MAX(id):
如果表的当前标识值小于存储在标识列中的最大标识值,则使用标识列中的最大值对其进行重置。请参阅随后的“例外”部分。
IDENT_CURRENT现在返回15。
一个简单的INSERT
声明:
INSERT INTO idT(uid, name) SELECT NEWID(), NEWID();
添加此行:
id uid name
16 B395D692-5D7B-4DFA-9971-A1497B8357A1 FF210D9E-4027-479C-B5D8-057E77FAF378
该id
列现在正在使用标识,新插入的值的确为16(15 + 1)。
这里有一个有关该SWITCH
技术的更多背景的相关问答:
如果要以新的身份值开始,则需要重新设置身份。看看有关的文档CHECKIDENT
DBCC CHECKIDENT (yourtable, reseed, starting point)
IDENTITY
吗