向现有 PK 添加自动增量

Hik*_*ari 16 sql-server primary-key auto-increment identity

我在另一个数据库中已经存在的数据库中创建了一个表。它最初是用旧的数据库数据填充的。表的 P​​K 必须接收那些记录中已经存在的值,因此它不能自动递增。

现在我需要新表将其 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 开始插入下一条记录idIDENTITY列。对于此示例,该列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列的种子,以便idid列中现有的最高数字之后恢复下一条记录:

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您删除和重新创建的列的其他事项。任何索引都必须删除并重新创建(就像我们对主键所做的那样)。请记住为您需要预先重新创建的每个索引和约束编写脚本。

  • 禁用表上的任何INSERTDELETE触发器。

如果重新创建表是一个选项:

如果您可以选择重新创建表格,那么一切都会简单得多:

  • 创建空表,将id列作为IDENTITY,
  • 摆好IDENTITY_INSERT ON餐桌,
  • 填充表,
  • 设置IDENTITY_INSERT OFF,并且
  • 重新设定身份。


Jul*_*eur 5

使用 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必须删除外键才能使用此技术。

分区开关

idTidT_Switch表具有兼容的结构。可以使用DELETE,UPDATEINSERT语句将行从idTtoidT_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技术的更多背景信息:

为什么不支持删除列上的 Identity 属性