Ajouter l'auto-incrémentation à PK existant


14

J'ai créé une table dans une base de données qui existe déjà dans une autre base de données. Il a été initialement rempli avec les anciennes données DB. Le PK de la table devait recevoir les valeurs qui existent déjà dans ces enregistrements, il ne pouvait donc pas s'agir d'une auto-incrémentation.

Maintenant, j'ai besoin de la nouvelle table pour avoir son PK comme auto-incrémentation. Mais comment puis-je faire cela après que le PK existe déjà et ait des données?


3
Lorsque vous dites "incrémentation automatique", à quoi faites-vous allusion exactement ? Dans SQL Server, il n'existe aucune propriété de ce type pour une colonne. Tu veux dire IDENTITY?
Max Vernon

Oui, c'est ainsi que cela s'appelle dans MSSQL. Dans la base de données en général, c'est un PK à auto-incrémentation.
Hikari

Réponses:


14

La façon dont je comprends votre question est que vous avez une table existante avec une colonne qui jusqu'à présent était remplie de valeurs manuelles, et maintenant vous voulez (1) faire de cette colonne une IDENTITY colonne, et (2) vous assurer que le IDENTITYdébut à partir de la valeur la plus récente des lignes existantes.

Tout d'abord, quelques données de test pour jouer avec:

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

L'objectif est de faire de la colonne de clé primaire de la table id, unIDENTITY colonne qui commencera à 21 pour le prochain enregistrement qui sera inséré. Pour cet exemple, la colonne xyzreprésente toutes les autres colonnes de la table.

Avant de faire quoi que ce soit, veuillez lire les avertissements au bas de cet article.

Tout d'abord, en cas de problème:

BEGIN TRANSACTION;

Maintenant, ajoutons une colonne de travail temporaire id_tempet définissons cette colonne sur les idvaleurs de la colonne existante :

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

Ensuite, nous devons supprimer la idcolonne existante (vous ne pouvez pas simplement "ajouter" un IDENTITYà une colonne existante, vous devez créer la colonne en tant que IDENTITY). La clé primaire doit également disparaître, car la colonne en dépend.

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

... et ajoutez à nouveau la colonne, cette fois sous la forme d'un IDENTITY, avec la clé primaire:

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

Voici où cela devient intéressant. Vous pouvez l'activer IDENTITY_INSERTsur la table, ce qui signifie que vous pouvez définir manuellement les valeurs d'une IDENTITYcolonne lorsque vous insérez de nouvelles lignes (sans mettre à jour les lignes existantes, cependant).

SET IDENTITY_INSERT dbo.ident_test ON;

Avec cet ensemble, DELETEtoutes les lignes de la table, mais les lignes que vous supprimez sont OUTPUTdirectement dans la même table - mais avec des valeurs spécifiques pour la idcolonne (à partir de la colonne de sauvegarde).

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

Une fois terminé, éteignez IDENTITY_INSERTà nouveau.

SET IDENTITY_INSERT dbo.ident_test OFF;

Déposez la colonne temporaire que nous avons ajoutée:

ALTER TABLE dbo.ident_test DROP COLUMN id_temp;

Et enfin, réamorcez la IDENTITYcolonne, de sorte que l'enregistrement suivant idreprendra après le nombre existant le plus élevé dans la idcolonne:

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

En vérifiant le tableau d'exemple, le idnombre le plus élevé est 20.

SELECT * FROM dbo.ident_test;

Ajoutez une autre ligne et vérifiez sa nouvelle IDENTITY:

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

Dans l'exemple, la nouvelle ligne aura id=21. Enfin, si vous êtes satisfait, validez la transaction:

COMMIT TRANSACTION;

Important

Ce n'est pas une opération banale, et elle comporte plusieurs risques dont vous devez être conscient.

  • Faites-le dans un environnement de test dédié. Avoir des sauvegardes. :)

  • J'aime utiliser BEGIN/COMMIT TRANSACTIONcar cela empêche d'autres processus de jouer avec la table pendant que vous êtes en train de la changer, et cela vous donne la possibilité de tout restaurer en cas de problème. Cependant, tout autre processus qui tente d'accéder à votre table avant d'avoir validé votre transaction finira par attendre. Cela peut être assez mauvais si vous avez une grande table et / ou si vous êtes dans un environnement de production.

  • OUTPUT .. INTOne fonctionnera pas si votre table cible a des contraintes de clé étrangère ou l'une des nombreuses autres fonctionnalités dont je ne me souviens pas du haut de ma tête. Vous pouvez à la place décharger les données dans une table temporaire, puis les réinsérer dans la table d'origine. Vous pourrez peut-être utiliser la commutation de partition (même si vous n'utilisez pas de partitions).

  • Exécutez ces instructions une par une, pas en tant que lot ou dans une procédure stockée.

  • Essayez de penser à d'autres choses qui peuvent dépendre de la idcolonne que vous supprimez et recréez. Tous les index devront être supprimés et recréés (comme nous l'avons fait avec la clé primaire). N'oubliez pas de scripter chaque index et contrainte que vous devrez recréer au préalable.

  • Désactivez les déclencheurs INSERTet DELETEsur la table.

Si la recréation de la table est une option:

Si la recréation de la table est une option pour vous, tout est beaucoup plus simple:

  • Créez la table vide, avec la idcolonne en tant que IDENTITY,
  • Set IDENTITY_INSERT ONpour la table,
  • Remplissez la table,
  • Set IDENTITY_INSERT OFF, et
  • Ressuscitez l'identité.

Excellente réponse, merci beaucoup! En effet, dans mon cas, je peux simplement le définir IDENTITY_INSERT ON, le remplir et le désactiver. C'est ce que je voulais faire, mais je ne savais pas que MSSQL le supportait.
Hikari

5

L'utilisation de UPDATE, DELETE ou INSERT pour déplacer des données peut prendre beaucoup de temps et utiliser des ressources (IO) sur les données et les fichiers journaux / disques. Il est possible d'éviter de remplir le journal des transactions avec potentiellement beaucoup d'enregistrements tout en travaillant sur une grande table: à l'aide de la commutation de partition, seules les métadonnées sont modifiées.

Aucun mouvement de données n'est impliqué et ceci est donc effectué très rapidement (presque instantanément).

Exemple de table

La question n'affiche pas le tableau DDL d'origine. Le DDL suivant sera utilisé comme exemple dans cette réponse:

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

Une demi-douzaine d'identifiants aléatoires factices de 0 à 15 sont ajoutés à cette requête:

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

Exemple de données dans 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

Nouvelle table avec IDENTITY(0, 1)

Le seul problème avec idTest le manque de la IDENTITY(0, 1)propriété sur id. Une nouvelle table avec une structure similaire IDENTITY(0, 1)est créée:

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

Mis à part IDENTITY(0, 1), idT_Switchest identique à idT.

Clés étrangères

Les clés étrangères idTdoivent être retirées pour permettre l'utilisation de cette technique.

Commutateur de partition

Les tableaux idTet idT_Switchont une structure compatible. Au lieu d'utiliser DELETE, UPDATEet des INSERTdéclarations de déplacer des lignes de idTla idT_Switchou sur idTlui - même, ALTER TABLE ... SWITCHpeut être utilisé:

ALTER TABLE dbo.idT
SWITCH TO dbo.idT_Switch;

La «partition» unique de PK_idT(la table entière) est déplacée vers PK_idT_Switch(et vice versa). idTcontient maintenant 0 lignes et idT_Switchcontient 6 lignes.

Vous pouvez trouver la liste complète des exigences de compatibilité source et destination ici:

Transfert efficace des données à l'aide de la commutation de partition

Notez que cette utilisation de SWITCHne nécessite pas Enterprise Edition, car il n'y a pas de partitionnement explicite. Une table non partitionnée est considérée comme une table avec une seule partition à partir de SQL Server 2005.

Remplacer idT

idT est maintenant vide et inutile et peut être supprimé:

DROP TABLE idT;

idT_Switchpeut être renommé et remplacera l'ancienne idTtable:

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

Clés étrangères

Les clés étrangères peuvent être ajoutées à nouveau à la nouvelle idTtable. Tout autre élément précédemment supprimé idTpour rendre les tables compatibles pour la commutation devra également être refait.

Réensemencer

SELECT IDENT_CURRENT( 'dbo.idT');

Cette commande renvoie 0. La table idT contient 6 lignes avec MAX (id) = 15. DBCC CHECKIDENT (nom_table) peut être utilisé:

DBCC CHECKIDENT ('dbo.idT');

Parce que 15 est plus grand que 0, il se réamorcera automatiquement sans chercher MAX (id):

Si la valeur d'identité actuelle d'une table est inférieure à la valeur d'identité maximale stockée dans la colonne d'identité, elle est réinitialisée à l'aide de la valeur maximale de la colonne d'identité. Voir la section «Exceptions» qui suit.

IDENT_CURRENT renvoie désormais 15 .

Testez et ajoutez des données

Une INSERTdéclaration simple :

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

Ajoute cette ligne:

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

La idcolonne utilise maintenant l'identité et la nouvelle valeur insérée est en effet 16 (15 + 1).

Plus d'information

Il y a une question et une réponse connexes avec plus d'informations sur la SWITCHtechnique ici:

Pourquoi la suppression de la propriété d'identité sur une colonne n'est pas prise en charge


4

Si vous souhaitez commencer avec une nouvelle valeur d'identité, vous devez réamorcer votre identité. Jetez un œil à la documentation deCHECKIDENT

DBCC CHECKIDENT (yourtable, reseed, starting point)

0

ACTIVER et DÉSACTIVER IDENTITY_INSERT

Si votre table est TABLE_A alors

  1. CREATE TABLE TABLE_B similaire à TABLE_A avec la colonne d'identité
  2. SET IDENTITY_INSERT TABLE_B ON
  3. INSÉRER dans TABLE_B à partir de TABLE_A
  4. SET IDENTITY_INSERT TABLE_B OFF
  5. DROP TABLE TABLE_A et renommez la table B Exec sp_rename 'TABLE_B', 'TABLE_A'
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.