向现有PK添加自动增量


14

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

现在,我需要新表将其PK作为自动增量。但是,在PK已经存在并且有数据之后,我该怎么办?


3
当你说“自动增量”什么究竟你指的是?在SQL Server中,列没有此类属性。你是说IDENTITY
Max Vernon

是的,这就是在MSSQL中的调用方式。通常,在数据库中,它是一个自动增量PK。
Hikari'2

Answers:


14

我理解您的问题的方式是,您有一个现有的表,该表的列到现在为止都已填充了手动值,现在您想要(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');

目标是创建表的主键列,idIDENTITY列将从要插入的下一条记录的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您要删除和重新创建的列的其他事项。任何索引都必须删除并重新创建(就像我们对主键所做的那样)。请记住为每个需要重新创建的索引和约束编写脚本。

  • 禁用任何INSERTDELETE触发器在桌子上。

如果重新创建表是一种选择:

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

  • 创建一个空表,该id列为IDENTITY
  • 设置IDENTITY_INSERT ON为表,
  • 填充表格,
  • 设置IDENTITY_INSERT OFF
  • 重新设定身份。

很好的答案,非常感谢!实际上,就我而言,我可以设置IDENTITY_INSERT ON,填充和禁用它。那就是我想做的,但是不知道MSSQL支持它。
Hikari'2

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);

此查询中添加了从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)

唯一的问题idTIDENTITY(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必须删除外键,才能使用此技术。

分区开关

idTidT_Switch表具有兼容的结构。而不是使用的DELETEUPDATEINSERT声明从移动行idTidT_SwitchidT本身,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技术的更多背景的相关问答:

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



0

启用和禁用IDENTITY_INSERT

如果您的表是TABLE_A,则

  1. 使用标识列创建类似于TABLE_A的TABLE TABLE_B
  2. 设置IDENTITY_INSERT TABLE_B为ON
  3. 从TABLE_A插入TABLE_B
  4. 设置IDENTITY_INSERT TABLE_B OFF
  5. 删除表TABLE_A并重命名表B执行sp_rename'TABLE_B','TABLE_A'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.