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 incrémentation automatique. Mais comment puis-je faire cela après que le PK existe déjà et ait des données?
La façon dont je comprends votre question est que vous avez une table existante avec une colonne qui jusqu'à présent a été remplie avec des valeurs manuelles, et maintenant vous voulez (1) faire de cette colonne une colonne IDENTITY
et (2 ) assurez-vous que IDENTITY
commence à partir de la valeur la plus récente des lignes existantes.
Tout d'abord, quelques données de test avec lesquelles jouer:
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');
Le but est de créer la colonne de clé primaire de la table, id
, une colonne IDENTITY
qui commencera à 21 pour le prochain enregistrement qui sera inséré. Pour cet exemple, la colonne xyz
repré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;
Ajoutons maintenant une colonne de travail temporaire, id_temp
et définissez cette colonne sur les valeurs existantes de la colonne id
:
ALTER TABLE dbo.ident_test ADD id_temp int NULL;
UPDATE dbo.ident_test SET id_temp=id;
Ensuite, nous devons supprimer la colonne id
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 activer IDENTITY_INSERT
sur la table, ce qui signifie que vous pouvez définir manuellement les valeurs d'une colonne IDENTITY
lorsque vous insérez de nouvelles lignes (mais pas en mettant à jour les lignes existantes).
SET IDENTITY_INSERT dbo.ident_test ON;
Avec cet ensemble, DELETE
toutes les lignes du tableau, mais les lignes que vous supprimez sont OUTPUT
directement dans le même tableau - mais avec des valeurs spécifiques pour le id
colonne (à 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é, tournez IDENTITY_INSERT
recule à 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 colonne IDENTITY
, afin que le prochain enregistrement id
reprenne après le nombre existant le plus élevé dans la colonne id
:
DECLARE @maxid int;
SELECT @maxid=MAX(id) FROM dbo.ident_test;
DBCC CHECKIDENT ("dbo.ident_test", RESEED, @maxid)
En vérifiant l'exemple de table, le nombre id
le plus élevé est 20.
SELECT * FROM dbo.ident_test;
Ajoutez une autre ligne et vérifiez son nouveau 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 TRANSACTION
car cela empêche les autres processus de jouer avec la table pendant que vous êtes en train de la changer, et vous donne la possibilité de tout annuler 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 .. INTO
ne 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. Au lieu de cela, vous pouvez 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 colonne id
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 tous les déclencheurs INSERT
et DELETE
sur la table.
Si recréer la table est une option:
Si la recréation de la table est une option pour vous, tout est beaucoup plus simple:
id
comme IDENTITY
,IDENTITY_INSERT ON
pour la table,IDENTITY_INSERT OFF
, etL'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).
La question n'affiche pas la table 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
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)
Le seul problème avec idT
est le manque de la propriété IDENTITY(0, 1)
sur id. Une nouvelle table avec une structure similaire et 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_Switch
Est identique à idT
.
Les clés étrangères sur idT
doivent être supprimées pour permettre l'utilisation de cette technique.
Les tables idT
et idT_Switch
Ont une structure compatible. Au lieu d'utiliser les instructions DELETE
, UPDATE
et INSERT
pour déplacer des lignes de idT
vers idT_Switch
Ou sur idT
lui-même , ALTER TABLE ... SWITCH
Peut ê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). idT
contient maintenant 0 lignes et idT_Switch
contient 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 SWITCH
ne 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.
idT
idT
est maintenant vide et inutile et peut être supprimé:
DROP TABLE idT;
idT_Switch
Peut être renommé et remplacera l'ancienne table idT
:
EXECUTE sys.sp_rename
@objname = N'dbo.idT_Switch',
@newname = N'idT', -- note lack of schema prefix
@objtype = 'OBJECT';
Les clés étrangères peuvent être ajoutées à nouveau à la nouvelle table idT
. Tout autre élément précédemment supprimé de idT
pour rendre les tables compatibles pour la commutation devra également être refait.
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 .
Une simple instruction INSERT
:
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 colonne id
utilise maintenant l'identité et la nouvelle valeur insérée est en effet 16 (15 + 1).
Il existe une question et une réponse connexes avec plus d'informations sur la technique SWITCH
ici:
Pourquoi la suppression de la propriété d'identité sur une colonne n'est pas prise en charge
Si vous souhaitez commencer avec une nouvelle valeur d'identité, vous devez réamorcer votre identité. Jetez un œil à la documentation de CHECKIDENT
DBCC CHECKIDENT (yourtable, reseed, starting point)
ACTIVER et DÉSACTIVER IDENTITY_INSERT
Si votre table est TABLE_A alors