Agregar autoincremento a PK existente

14

Creé una tabla en un DB que ya existe en otro DB. Inicialmente se rellenó con los datos antiguos de DB. El PK de la tabla tenía que recibir los valores que ya existen en esos registros, por lo que no podría ser autoincrement.

Ahora necesito que la nueva tabla tenga su PK como autoincremento. Pero, ¿cómo puedo hacer eso después de que el PK ya exista y tenga datos?

Hikari
fuente
3
Cuando dices "autoincremento", ¿a qué te refieres exactamente ? En SQL Server no existe tal propiedad para una columna. Qué quiere decir IDENTITY?
Max Vernon
Sí, así se llama en MSSQL. En la base de datos en general, es un PK de autoincremento.
Hikari

Respuestas:

14

La forma en que entiendo su pregunta es que tiene una tabla existente con una columna que hasta ahora se ha rellenado con valores manuales, y ahora desea (1) hacer que esta columna sea una IDENTITYcolumna y (2) asegurarse de que IDENTITYcomience desde el valor más reciente en las filas existentes.

En primer lugar, algunos datos de prueba para jugar:

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

El objetivo es crear la columna de clave principal de la tabla id, una IDENTITYcolumna que comenzará en 21 para el siguiente registro que se inserte. Para este ejemplo, la columna xyzrepresenta todas las otras columnas de la tabla.

Antes de hacer nada, lea las advertencias al final de esta publicación.

En primer lugar, en caso de que algo salga mal:

BEGIN TRANSACTION;

Ahora, agreguemos una columna de trabajo temporal id_tempy configuremos esa columna a los idvalores de la columna existente :

ALTER TABLE dbo.ident_test ADD id_temp int NULL;
UPDATE dbo.ident_test SET id_temp=id;

A continuación, debemos descartar la idcolumna existente (no puede simplemente "agregar" una IDENTITYa una columna existente, debe crear la columna como una IDENTITY). La clave principal también tiene que ir, porque la columna depende de ella.

ALTER TABLE dbo.ident_test DROP CONSTRAINT PK_ident_test;
ALTER TABLE dbo.ident_test DROP COLUMN id;

... y agregue la columna nuevamente, esta vez como IDENTITY, junto con la clave principal:

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

Aquí es donde se pone interesante. Puede habilitar IDENTITY_INSERTen la tabla, lo que significa que puede definir manualmente los valores de una IDENTITYcolumna cuando inserta nuevas filas (sin embargo, no actualiza las filas existentes).

SET IDENTITY_INSERT dbo.ident_test ON;

Con ese conjunto, DELETEtodas las filas de la tabla, pero las filas que está eliminando están OUTPUTen la misma tabla, pero con valores específicos para la idcolumna (de la columna de copia de seguridad).

DELETE FROM dbo.ident_test
OUTPUT deleted.id_temp AS id, deleted.xyz
INTO dbo.ident_test (id, xyz);

Una vez hecho, IDENTITY_INSERTapague nuevamente.

SET IDENTITY_INSERT dbo.ident_test OFF;

Suelte la columna temporal que agregamos:

ALTER TABLE dbo.ident_test DROP COLUMN id_temp;

Y finalmente, vuelva a colocar la IDENTITYcolumna, para que los siguientes registros idse reanuden después del número más alto existente en la idcolumna:

DECLARE @maxid int;
SELECT @maxid=MAX(id) FROM dbo.ident_test;
DBCC CHECKIDENT ("dbo.ident_test", RESEED, @maxid)

Verificando la tabla de ejemplo, el idnúmero más alto es 20.

SELECT * FROM dbo.ident_test;

Agregue otra fila y verifique su nuevo IDENTITY:

INSERT INTO dbo.ident_test (xyz) VALUES ('New row');
SELECT * FROM dbo.ident_test;

En el ejemplo, la nueva fila tendrá id=21. Finalmente, si estás contento, confirma la transacción:

COMMIT TRANSACTION;

Importante

Esta no es una operación trivial, y conlleva bastantes riesgos que debe tener en cuenta.

  • Haga esto en un entorno de prueba dedicado. Tener copias de seguridad. :)

  • Me gusta usarlo BEGIN/COMMIT TRANSACTIONporque evita que otros procesos se enreden con la tabla mientras estás en medio de cambiarla, y te da la posibilidad de deshacer todo si algo sale mal. Sin embargo, cualquier otro proceso que intente acceder a su tabla antes de que haya confirmado su transacción terminará esperando. Esto puede ser bastante malo si tiene una mesa grande y / o está en un entorno de producción.

  • OUTPUT .. INTOno funcionará si su tabla de destino tiene restricciones de clave externa o cualquiera de una serie de otras características que no puedo recordar en la parte superior de mi cabeza. En su lugar, puede descargar los datos en una tabla temporal y luego volver a insertarlos en la tabla original. Es posible que pueda usar el cambio de partición (incluso si no usa particiones).

  • Ejecute estas declaraciones una por una, no como un lote o en un procedimiento almacenado.

  • Intente pensar en otras cosas que pueden depender de la idcolumna que está soltando y volviendo a crear. Cualquier índice tendrá que descartarse y volver a crearse (como hicimos con la clave primaria). Recuerde escribir cada índice y restricción que necesitará recrear de antemano.

  • Deshabilita cualquiera INSERTy DELETEdisparadores en la mesa.

Si volver a crear la tabla es una opción:

Si volver a crear la tabla es una opción para usted, todo es mucho más simple:

  • Cree la tabla vacía, con la idcolumna como un IDENTITY,
  • Poner IDENTITY_INSERT ONpara la mesa,
  • Poblar la mesa,
  • Conjunto IDENTITY_INSERT OFF, y
  • Reseed la identidad.
Daniel Hutmacher
fuente
Gran respuesta, muchas gracias! De hecho, en mi caso solo puedo configurarlo IDENTITY_INSERT ON, rellenarlo y desactivarlo. Eso es lo que quería hacer, pero no sabía que MSSQL lo soportaba.
Hikari
5

Usar ACTUALIZAR, ELIMINAR o INSERTAR para mover datos puede llevar bastante tiempo y usar recursos (IO) tanto en datos como en archivos / discos de registro. Es posible evitar llenar el registro de transacciones con potencialmente muchos registros mientras se trabaja en una tabla grande: mediante el cambio de partición, solo se modifican los metadatos.

No hay movimiento de datos involucrados y, por lo tanto, esto se realiza muy rápido (casi instantáneo).

Tabla de muestra

La pregunta no muestra la tabla original DDL. El siguiente DDL se usará como ejemplo en esta respuesta:

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

Se agregan media docena de identificadores aleatorios ficticios de 0 a 15 con esta consulta:

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

Datos de ejemplo en 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

Nueva mesa con IDENTITY(0, 1)

El único problema con idTes la falta de la IDENTITY(0, 1)propiedad en la identificación. Se IDENTITY(0, 1)crea una nueva tabla con una estructura similar :

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

Aparte de IDENTITY(0, 1), idT_Switches idéntico a idT.

Llaves extranjeras

idTDeben eliminarse las claves foráneas activadas para permitir el uso de esta técnica.

Interruptor de partición

Las tablas idTy idT_Switchtienen una estructura compatible. En lugar de utilizar DELETE, UPDATEy INSERTlas declaraciones de mover filas de idTa idT_Switcho sobre idTsí mismo, ALTER TABLE ... SWITCHse puede utilizar:

ALTER TABLE dbo.idT
SWITCH TO dbo.idT_Switch;

La única 'partición' de PK_idT(toda la tabla) se mueve a PK_idT_Switch(y viceversa). idTahora contiene 0 filas y idT_Switchcontiene 6 filas.

Puede encontrar la lista completa de requisitos de compatibilidad de origen y destino aquí:

Transferencia de datos eficientemente mediante el cambio de partición

Tenga en cuenta que este uso de SWITCHno requiere Enterprise Edition, porque no hay particiones explícitas. Se considera que una tabla sin particiones es una tabla con una única partición desde SQL Server 2005 en adelante.

Reemplazar idT

idT ahora está vacío e inútil y puede descartarse:

DROP TABLE idT;

idT_Switchse puede renombrar y reemplazará la idTtabla anterior:

EXECUTE sys.sp_rename
    @objname = N'dbo.idT_Switch',
    @newname = N'idT', -- note lack of schema prefix
    @objtype = 'OBJECT';

Llaves extranjeras

Las claves externas se pueden agregar nuevamente a la nueva idTtabla. Cualquier otra cosa que se haya eliminado previamente idTpara hacer que las tablas sean compatibles para el cambio también deberá rehacerse.

Reseed

SELECT IDENT_CURRENT( 'dbo.idT');

Este comando devuelve 0. La tabla idT contiene 6 filas con MAX (id) = 15. Se puede usar DBCC CHECKIDENT (nombre_tabla) :

DBCC CHECKIDENT ('dbo.idT');

Como 15 es mayor que 0, se reiniciará automáticamente sin buscar MAX (id):

Si el valor de identidad actual para una tabla es menor que el valor de identidad máximo almacenado en la columna de identidad, se restablece utilizando el valor máximo en la columna de identidad. Consulte la sección 'Excepciones' que sigue.

IDENT_CURRENT ahora devuelve 15 .

Probar y agregar datos

Una INSERTdeclaración simple :

INSERT INTO idT(uid, name) SELECT NEWID(), NEWID();

Agrega esta fila:

id  uid                                     name
16  B395D692-5D7B-4DFA-9971-A1497B8357A1    FF210D9E-4027-479C-B5D8-057E77FAF378

La idcolumna ahora está usando la identidad y el valor recién insertado es de hecho 16 (15 + 1).

Más información

Hay una pregunta y respuesta relacionada con más antecedentes sobre la SWITCHtécnica aquí:

¿Por qué no se admite la eliminación de la propiedad Identity en una columna?

Julien Vavasseur
fuente
4

Si desea comenzar con un nuevo valor de identidad, debe reiniciar su identidad. Echa un vistazo a la documentación paraCHECKIDENT

DBCC CHECKIDENT (yourtable, reseed, starting point)
Tom V - prueba topanswers.xyz
fuente
0

ENABLE y DISABLE IDENTITY_INSERT

Si su tabla es TABLE_A entonces

  1. CREATE TABLE TABLE_B similar a TABLE_A con la columna de identidad
  2. SET IDENTITY_INSERT TABLE_B ON
  3. INSERTAR en TABLE_B desde TABLE_A
  4. SET IDENTITY_INSERT TABLE_B OFF
  5. DROP TABLE TABLE_A y renombrar la tabla B Exec sp_rename 'TABLE_B', 'TABLE_A'
usuario4321
fuente