Hik*_*ari 16 sql-server primary-key auto-increment identity
我在另一个数据库中已经存在的数据库中创建了一个表。它最初是用旧的数据库数据填充的。表的 PK 必须接收那些记录中已经存在的值,因此它不能自动递增。
现在我需要新表将其 PK 作为自动增量。但是在 PK 已经存在并且有数据之后我该怎么做呢?
Dan*_*her 17
我理解您的问题的方式是,您有一个现有表,其中有一列直到现在都填充了手动值,现在您想要 (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');
Run Code Online (Sandbox Code Playgroud)
目标是使表的主键列,从 21 开始插入下一条记录id
的IDENTITY
列。对于此示例,该列xyz
代表表的所有其他列。
在你做任何事情之前,请阅读这篇文章底部的警告。
首先,万一出现问题:
BEGIN TRANSACTION;
Run Code Online (Sandbox Code Playgroud)
现在,让我们添加一个临时工作列,id_temp
并将该列设置为现有id
列的值:
ALTER TABLE dbo.ident_test ADD id_temp int NULL;
UPDATE dbo.ident_test SET id_temp=id;
Run Code Online (Sandbox Code Playgroud)
接下来,我们需要删除现有id
列(您不能只是“添加”IDENTITY
一个现有列,您必须将列创建为IDENTITY
)。主键也必须去,因为列依赖于它。
ALTER TABLE dbo.ident_test DROP CONSTRAINT PK_ident_test;
ALTER TABLE dbo.ident_test DROP COLUMN id;
Run Code Online (Sandbox Code Playgroud)
...并再次添加该列,这次是作为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);
Run Code Online (Sandbox Code Playgroud)
这就是有趣的地方。您可以IDENTITY_INSERT
在表上启用,这意味着您可以IDENTITY
在插入新行(但不更新现有行)时手动定义列的值。
SET IDENTITY_INSERT dbo.ident_test ON;
Run Code Online (Sandbox Code Playgroud)
使用该集合,DELETE
表中的所有行,但您要删除的行都OUTPUT
在同一个表中 - 但具有特定的id
列值(来自备份列)。
DELETE FROM dbo.ident_test
OUTPUT deleted.id_temp AS id, deleted.xyz
INTO dbo.ident_test (id, xyz);
Run Code Online (Sandbox Code Playgroud)
完成IDENTITY_INSERT
后,再次关闭。
SET IDENTITY_INSERT dbo.ident_test OFF;
Run Code Online (Sandbox Code Playgroud)
删除我们添加的临时列:
ALTER TABLE dbo.ident_test DROP COLUMN id_temp;
Run Code Online (Sandbox Code Playgroud)
最后,重新设置该IDENTITY
列的种子,以便id
在id
列中现有的最高数字之后恢复下一条记录:
DECLARE @maxid int;
SELECT @maxid=MAX(id) FROM dbo.ident_test;
DBCC CHECKIDENT ("dbo.ident_test", RESEED, @maxid)
Run Code Online (Sandbox Code Playgroud)
查看示例表,最大id
数字为 20。
SELECT * FROM dbo.ident_test;
Run Code Online (Sandbox Code Playgroud)
添加另一行并检查其新行IDENTITY
:
INSERT INTO dbo.ident_test (xyz) VALUES ('New row');
SELECT * FROM dbo.ident_test;
Run Code Online (Sandbox Code Playgroud)
在该示例中,新行将具有id=21
. 最后,如果您满意,请提交事务:
COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)
重要的
这不是一个微不足道的操作,它带有很多您应该注意的风险。
在专用的测试环境中执行此操作。有备份。:)
我喜欢使用BEGIN/COMMIT TRANSACTION
它是因为它可以防止其他进程在您更改表格时弄乱表格,并且如果出现问题,它可以让您回滚所有内容。但是,在您提交事务之前尝试访问您的表的任何其他进程最终都会等待。如果您有一个大表和/或您在生产环境中,这可能非常糟糕。
OUTPUT .. INTO
如果您的目标表具有外键约束或任何其他一些我一时想不起来的功能,则将无法工作。您可以改为将数据卸载到临时表中,然后将其插入回原始表中。您也许可以使用分区切换(即使您不使用分区)。
一个接一个地运行这些语句,而不是批处理或在存储过程中。
尝试考虑可能取决于id
您删除和重新创建的列的其他事项。任何索引都必须删除并重新创建(就像我们对主键所做的那样)。请记住为您需要预先重新创建的每个索引和约束编写脚本。
禁用表上的任何INSERT
和DELETE
触发器。
如果重新创建表是一个选项:
如果您可以选择重新创建表格,那么一切都会简单得多:
id
列作为IDENTITY
,IDENTITY_INSERT ON
餐桌,IDENTITY_INSERT OFF
,并且使用 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);
Run Code Online (Sandbox Code Playgroud)
这个查询添加了六个从 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
Run Code Online (Sandbox Code Playgroud)
IdT
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);
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
除此之外IDENTITY(0, 1)
,idT_Switch
与 相同idT
。
idT
必须删除外键才能使用此技术。
在idT
与idT_Switch
表具有兼容的结构。可以使用DELETE
,UPDATE
和INSERT
语句将行从idT
toidT_Switch
或 onidT
自身移动,而不是ALTER TABLE ... SWITCH
使用:
ALTER TABLE dbo.idT
SWITCH TO dbo.idT_Switch;
Run Code Online (Sandbox Code Playgroud)
PK_idT
(整个表)的单个“分区”被移动到PK_idT_Switch
(反之亦然)。idT
现在包含 0 行并idT_Switch
包含 6 行。
您可以在此处找到源和目标兼容性要求的完整列表:
注意这种使用SWITCH
不需要企业版,因为没有明确的分区。从 SQL Server 2005 开始,未分区的表被视为具有单个分区的表。
idT
idT
现在是空的和无用的,可以删除:
DROP TABLE idT;
Run Code Online (Sandbox Code Playgroud)
idT_Switch
可以重命名并将替换旧idT
表:
EXECUTE sys.sp_rename
@objname = N'dbo.idT_Switch',
@newname = N'idT', -- note lack of schema prefix
@objtype = 'OBJECT';
Run Code Online (Sandbox Code Playgroud)
外键可以再次添加到新idT
表中。之前idT
为使表兼容切换而删除的任何其他内容也需要重做。
SELECT IDENT_CURRENT( 'dbo.idT');
Run Code Online (Sandbox Code Playgroud)
此命令返回 0。表 idT 包含 6 行,其中 MAX(id) = 15。可以使用DBCC CHECKIDENT ( table_name ):
DBCC CHECKIDENT ('dbo.idT');
Run Code Online (Sandbox Code Playgroud)
因为 15 比 0 大,它会自动重新播种而不用寻找 MAX(id):
如果表的当前标识值小于标识列中存储的最大标识值,则使用标识列中的最大值对其进行重置。请参阅后面的“例外”部分。
IDENT_CURRENT 现在返回15。
一个简单的INSERT
声明:
INSERT INTO idT(uid, name) SELECT NEWID(), NEWID();
Run Code Online (Sandbox Code Playgroud)
添加这一行:
id uid name
16 B395D692-5D7B-4DFA-9971-A1497B8357A1 FF210D9E-4027-479C-B5D8-057E77FAF378
Run Code Online (Sandbox Code Playgroud)
该id
列现在正在使用标识,新插入的值确实是 16 (15+1)。
这里有一个相关的问题和答案,其中包含有关该SWITCH
技术的更多背景信息: