Eliminar identidad de una columna en una tabla

127

Tenemos una tabla de 5 GB (casi 500 millones de filas) y queremos eliminar la propiedad de identidad en una de las columnas, pero cuando intentamos hacer esto a través de SSMS, se agota el tiempo de espera.

¿Se puede hacer esto a través de T-SQL?

Conrad Jagger
fuente
1
¿Puedes publicar el esquema de la tabla aquí?
Al W
Estoy seguro de que hay excelentes razones por las que SQL Server no admite la eliminación de una propiedad de identidad de una columna a través de una simple ALTER TABLE ..., pero de todos modos me entristece que ese sea el caso.
Jon Schneider

Respuestas:

143

No puede eliminar una IDENTITYespecificación una vez establecida.

Para eliminar toda la columna:

ALTER TABLE yourTable
DROP COLUMN yourCOlumn;

Información sobre ALTER TABLE aquí

Si necesita conservar los datos, pero eliminar la IDENTITYcolumna, deberá:

  • Crea una nueva columna
  • Transfiera los datos de la IDENTITYcolumna existente a la nueva columna
  • Descarte la IDENTITYcolumna existente .
  • Cambie el nombre de la nueva columna al nombre de la columna original.
Adam Wenger
fuente
3
puede eliminar una especificación de identidad. De hecho, tuve que hacerlo ayer usando SSMS aunque no en 500 millones de filas.
Simon
33
@simon si escribe sus cambios, verá que SSMS está creando una copia de la tabla sin la propiedad de identidad.
Code Magician el
3
Solo quiero agregar para cambiar el nombre de la nueva columna al nombre de la columna original. Además, si esta identitycolumna se usa como parte de una foreign keyen otra tabla, primero tendrá que eliminar las restricciones, luego tomar medidas como @AdamWenger mencionó sobre la eliminación de la identidad attribute/property... También puede consultar este enlace para obtener más detalles sobre eliminar solo el atributo: blog.sqlauthority.com/2009/05/03/… .. ¡Buena suerte!
Nonym
1
Para la persona que votó en contra, agradecería saber lo que no le gustó de mi respuesta.
Adam Wenger
1
Echa un vistazo a la respuesta de Mark Sowul a continuación. Elimina la necesidad de mover datos de columna a columna. Usando la respuesta de Mark solo estás barajando metadatos. No es gran cosa a menos que esté trabajando en una tabla que tenga decenas o cientos de millones de filas. Además, la respuesta de Mark evita la reubicación de la columna en el esquema de la tabla. Lo probé y funcionó a las mil maravillas. Muy inteligente.
Andrew Steitz
100

Si desea hacer esto sin agregar y completar una nueva columna , sin reordenar las columnas y casi sin tiempo de inactividad porque no hay datos cambiando en la tabla, hagamos un poco de magia con la funcionalidad de particionamiento (pero como no se utilizan particiones, no debe hacerlo) no necesita edición Enterprise):

  1. Elimine todas las claves externas que apuntan a esta tabla
  2. Script de la tabla que se creará; cambie el nombre de todo, por ejemplo, 'MyTable2', 'MyIndex2', etc. Elimine la especificación IDENTITY.
  3. Ahora debería tener dos tablas "idénticas", una llena, la otra vacía sin IDENTIDAD.
  4. correr ALTER TABLE [Original] SWITCH TO [Original2]
  5. Ahora su tabla original estará vacía y la nueva tendrá los datos. Ha cambiado los metadatos de las dos tablas (instantáneo).
  6. Suelte el original (tabla ahora vacía), exec sys.sp_renamepara cambiar el nombre de los diversos objetos de esquema a los nombres originales, y luego puede recrear sus claves foráneas.

Por ejemplo, dado:

CREATE TABLE Original
(
  Id INT IDENTITY PRIMARY KEY
, Value NVARCHAR(300)
);
CREATE NONCLUSTERED INDEX IX_Original_Value ON Original (Value);

INSERT INTO Original
SELECT 'abcd'
UNION ALL 
SELECT 'defg';

Puedes hacer lo siguiente:

--create new table with no IDENTITY
CREATE TABLE Original2
(
  Id INT PRIMARY KEY
, Value NVARCHAR(300)
);
CREATE NONCLUSTERED INDEX IX_Original_Value2 ON Original2 (Value);

--data before switch
SELECT 'Original', *
FROM Original
UNION ALL
SELECT 'Original2', *
FROM Original2;

ALTER TABLE Original SWITCH TO Original2;

--data after switch
SELECT 'Original', *
FROM Original
UNION ALL
SELECT 'Original2', *
FROM Original2;

--clean up 
IF NOT EXISTS (SELECT * FROM Original) DROP TABLE Original;
EXEC sys.sp_rename 'Original2.IX_Original_Value2', 'IX_Original_Value', 'INDEX';
EXEC sys.sp_rename 'Original2', 'Original', 'OBJECT';


UPDATE Original
SET Id = Id + 1;

SELECT *
FROM Original;
Mark Sowul
fuente
No tengo tiempo para intentarlo, pero es bueno saberlo la próxima vez que realmente necesite soltar una identidad.
CoderDennis
11
Esta debería ser la respuesta aceptada. Es la única forma real de eliminar una columna de identidad sin una migración masiva de datos.
Vaccano
¡Guauu! Esto es muy inteligente.
Andrew Steitz
¡Esta respuesta es fenomenal! ¡Gracias!
Jon
55
Si usa SQL Management Studio para crear una secuencia de comandos de la tabla, asegúrese de activar Herramientas> Opciones> Explorador de objetos de SQL Server> Secuencias de comandos> Opciones de tabla y vista> Índices de secuencia de comandos (Falso de forma predeterminada)
usuario423430
61

Esto se complica con las restricciones de clave principal y externa, así que aquí hay algunos scripts para ayudarlo en su camino:

Primero, cree una columna duplicada con un nombre temporal:

alter table yourTable add tempId int NOT NULL default -1;
update yourTable set tempId = id;

A continuación, obtenga el nombre de su restricción de clave principal:

SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = 'yourTable';

Ahora intente eliminar la restricción de clave principal para su columna:

ALTER TABLE yourTable DROP CONSTRAINT PK_yourTable_id;

Si tiene claves foráneas, fallará, por lo que, si es así, elimine las restricciones de claves foráneas. ¡MANTENGA LA PISTA DE LAS TABLAS PARA LAS QUE FUNCIONA ESTO PARA QUE PUEDE AGREGAR LAS RESTRICCIONES MÁS TARDE!

SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = 'otherTable';
alter table otherTable drop constraint fk_otherTable_yourTable;
commit;
..

Una vez que se hayan eliminado todas las restricciones de clave externa, podrá eliminar la restricción PK, soltar esa columna, cambiar el nombre de su columna temporal y agregar la restricción PK a esa columna:

ALTER TABLE yourTable DROP CONSTRAINT PK_yourTable_id;
alter table yourTable drop column id;
EXEC sp_rename 'yourTable.tempId', 'id', 'COLUMN';
ALTER TABLE yourTable ADD CONSTRAINT PK_yourTable_id PRIMARY KEY (id) 
commit;

Finalmente, agregue las restricciones FK nuevamente en:

alter table otherTable add constraint fk_otherTable_yourTable foreign key (yourTable_id) references yourTable(id);
..

El Fin!

Briguy37
fuente
preste atención a su versión de servidor sql, probé en azure sql server, no todas las operaciones aquí son compatibles con azure versión sql.
dasons
¡Gracias por la respuesta! ¡Me ayudo mucho! Solo debe agregar una verificación para los índices que hacen referencia a la columna. Algo así como: Select t.name 'Table', i.name 'Index', c.name 'Column', i.is_primary_key, i.is_unique From sys.tables t Inner Join sys.indexes i On i.object_id = t.object_id Inner Join sys.index_columns ic ON ic.object_id = i.object_id And i.index_id = ic.index_id Inner Join sys.columns c ON ic.object_id = c.object_id and ic.column_id = c.column_id Where t.name = 'tableName' Order By t.name, i.name, i.index_id, ic.index_column_id
Alexandre Junges
buen truco, me ayudó mucho, pero un punto, quiero mencionar aquí es que, cuando agregamos una nueva columna duplicada como se mencionó en el primer paso, se agregó al final, pero generalmente queremos que nuestra columna de clave principal, es decir, Id la 1ra columna en esa tabla. Entonces, ¿hay alguna solución para eso?
Ashish Shukla
19

Acabo de tener este mismo problema. 4 declaraciones en SSMS en lugar de usar la GUI y fue muy rápido.

  • Hacer una nueva columna

    alter table users add newusernum int;

  • Copiar valores sobre

    update users set newusernum=usernum;

  • Suelta la columna vieja

    alter table users drop column usernum;

  • Cambie el nombre de la nueva columna al nombre de la columna anterior

    EXEC sp_RENAME 'users.newusernum' , 'usernum', 'COLUMN';

libertad_
fuente
No es tan simple si tiene alguna restricción en la columna anterior.
Suncat2000
13

El siguiente script elimina el campo de identidad para una columna llamada 'Id'

Espero eso ayude.

BEGIN TRAN
BEGIN TRY
    EXEC sp_rename '[SomeTable].[Id]', 'OldId';

    ALTER TABLE [SomeTable] ADD Id int NULL

    EXEC ('UPDATE [SomeTable] SET Id = OldId')

    ALTER TABLE [SomeTable] NOCHECK CONSTRAINT ALL

    ALTER TABLE [SomeTable] DROP CONSTRAINT [PK_constraintName];
    ALTER TABLE [SomeTable] DROP COLUMN OldId
    ALTER TABLE [SomeTable] ALTER COLUMN [Id] INTEGER NOT NULL
    ALTER TABLE [SomeTable] ADD CONSTRAINT PK_JobInfo PRIMARY KEY (Id)

    ALTER TABLE [SomeTable] CHECK CONSTRAINT ALL

    COMMIT TRAN
END TRY
BEGIN CATCH
    ROLLBACK TRAN   
    SELECT ERROR_MESSAGE ()
END CATCH
bluedot
fuente
4

El siguiente código funciona bien cuando no conocemos el nombre de la columna de identidad .

Necesita copiar datos en una nueva tabla temporal como Invoice_DELETED. y la próxima vez que usemos:

insert into Invoice_DELETED select * from Invoice where ...


SELECT t1.*
INTO Invoice_DELETED
FROM Invoice t1
LEFT JOIN Invoice ON 1 = 0
--WHERE t1.InvoiceID = @InvoiceID

Para obtener más explicaciones, consulte: https://dba.stackexchange.com/a/138345/101038

Zolfaghari
fuente
1
AMOR YAAAAAAAAAA. Exactamente lo que estaba buscando, una manera fácil de evitar generar "IDENTIDAD" a partir de un "SELECCIONAR * EN" (para crear una nueva tabla de temperatura para las copias de seguridad que necesitan)
KurzedMetal
es pequeño y hermoso;)
Zolfaghari
3
ALTER TABLE tablename add newcolumn int
update tablename set newcolumn=existingcolumnname
ALTER TABLE tablename DROP COLUMN existingcolumnname;
EXEC sp_RENAME 'tablename.oldcolumn' , 'newcolumnname', 'COLUMN'

Sin embargo, el código anterior solo funciona si no hay una relación de clave primaria-externa

Jekin Kalariya
fuente
1

Solo para alguien que tiene el mismo problema que yo. Si solo desea hacer una inserción solo una vez, puede hacer algo como esto.

Supongamos que tiene una tabla con dos columnas.

ID Identity (1,1) | Name Varchar

y desea insertar una fila con el ID = 4. Entonces, vuelva a colocarla en 3 para que la siguiente sea 4

DBCC CHECKIDENT([YourTable], RESEED, 3)

Hacer el inserto

INSERT  INTO [YourTable]
        ( Name )
VALUES  ( 'Client' )

Y devuelva su semilla a la ID más alta, supongamos que es 15

DBCC CHECKIDENT([YourTable], RESEED, 15)

¡Hecho!

Faruk Feres
fuente
1

Tenía el mismo requisito, y podría intentarlo de esta manera, que personalmente le recomiendo, diseñe manualmente su tabla y genere el script, y lo que hice a continuación fue cambiar el nombre de la tabla anterior y también su restricción para la copia de seguridad.

/* To prevent any potential data loss issues, you should review this script in detail before running it outside the context of the database designer.*/
BEGIN TRANSACTION

SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
ALTER TABLE dbo.SI_Provider_Profile
    DROP CONSTRAINT DF_SI_Provider_Profile_SIdtDateTimeStamp
GO
ALTER TABLE dbo.SI_Provider_Profile
    DROP CONSTRAINT DF_SI_Provider_Profile_SIbHotelPreLoaded
GO
CREATE TABLE dbo.Tmp_SI_Provider_Profile
    (
    SI_lProvider_Profile_ID int NOT NULL,
    SI_lSerko_Integrator_Token_ID int NOT NULL,
    SI_sSerko_Integrator_Provider varchar(50) NOT NULL,
    SI_sSerko_Integrator_Profile varchar(50) NOT NULL,
    SI_dtDate_Time_Stamp datetime NOT NULL,
    SI_lProvider_ID int NULL,
    SI_sDisplay_Name varchar(10) NULL,
    SI_lPurchased_From int NULL,
    SI_sProvider_UniqueID varchar(255) NULL,
    SI_bHotel_Pre_Loaded bit NOT NULL,
    SI_sSiteName varchar(255) NULL
    )  ON [PRIMARY]
GO
ALTER TABLE dbo.Tmp_SI_Provider_Profile SET (LOCK_ESCALATION = TABLE)
GO
ALTER TABLE dbo.Tmp_SI_Provider_Profile ADD CONSTRAINT
    DF_SI_Provider_Profile_SIdtDateTimeStamp DEFAULT (getdate()) FOR SI_dtDate_Time_Stamp
GO
ALTER TABLE dbo.Tmp_SI_Provider_Profile ADD CONSTRAINT
    DF_SI_Provider_Profile_SIbHotelPreLoaded DEFAULT ((0)) FOR SI_bHotel_Pre_Loaded
GO
IF EXISTS(SELECT * FROM dbo.SI_Provider_Profile)
        EXEC('INSERT INTO dbo.Tmp_SI_Provider_Profile (SI_lProvider_Profile_ID, SI_lSerko_Integrator_Token_ID, SI_sSerko_Integrator_Provider, SI_sSerko_Integrator_Profile, SI_dtDate_Time_Stamp, SI_lProvider_ID, SI_sDisplay_Name, SI_lPurchased_From, SI_sProvider_UniqueID, SI_bHotel_Pre_Loaded, SI_sSiteName)
        SELECT SI_lProvider_Profile_ID, SI_lSerko_Integrator_Token_ID, SI_sSerko_Integrator_Provider, SI_sSerko_Integrator_Profile, SI_dtDate_Time_Stamp, SI_lProvider_ID, SI_sDisplay_Name, SI_lPurchased_From, SI_sProvider_UniqueID, SI_bHotel_Pre_Loaded, SI_sSiteName FROM dbo.SI_Provider_Profile WITH (HOLDLOCK TABLOCKX)')
GO

-- Rename the primary key constraint or unique key In SQL Server constraints such as primary keys or foreign keys are objects in their own right, even though they are dependent upon the "containing" table.
EXEC sp_rename 'dbo.SI_Provider_Profile.PK_SI_Provider_Profile', 'PK_SI_Provider_Profile_Old';
GO
-- backup old table in case of 
EXECUTE sp_rename N'dbo.SI_Provider_Profile', N'SI_Provider_Profile_Old', 'OBJECT'
GO

EXECUTE sp_rename N'dbo.Tmp_SI_Provider_Profile', N'SI_Provider_Profile', 'OBJECT'
GO

ALTER TABLE dbo.SI_Provider_Profile ADD CONSTRAINT
    PK_SI_Provider_Profile PRIMARY KEY NONCLUSTERED 
    (
    SI_lProvider_Profile_ID
    ) WITH( PAD_INDEX = OFF, FILLFACTOR = 90, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO
COMMIT TRANSACTION
Cena Jang
fuente
1

En SQL Server puede activar y desactivar la inserción de identidad de esta manera:

SET IDENTITY_INSERT table_name ON

- ejecuta tus consultas aquí

SET IDENTITY_INSERT table_name OFF

usuario2624918
fuente