web-dev-qa-db-fra.com

SQL Server - Ajout d'une colonne non nullable à une table existante - Publication SSDT

En raison de la logique métier, nous avons besoin d'une nouvelle colonne dans une table qui est essentielle pour garantir qu'elle est toujours remplie. Par conséquent, il doit être ajouté au tableau en tant que NOT NULL. Contrairement à questions précédentes qui expliquent comment faire cela manuellement , cela doit être géré par la publication SSDT.

Je me suis cogné la tête contre le mur pendant un certain temps au cours de cette tâche à consonance simple en raison de certaines réalisations:

  1. Une valeur par défaut n'est pas appropriée et ne peut pas être une colonne calculée. Il s'agit peut-être d'une colonne de clé étrangère, mais pour d'autres, nous ne pouvons pas utiliser une fausse valeur comme 0 ou -1 car ces valeurs peuvent avoir une signification (par exemple, des données numériques).
  2. L'ajout de la colonne dans un script de pré-déploiement échouera la publication lorsqu'il essaiera automatiquement de créer la même colonne, une deuxième fois (même si le script de pré-déploiement est écrit pour être idempotent) (celui-ci est vraiment aggravant car je peux sinon penser à une solution facile)
  3. La modification de la colonne sur NON NUL dans un script de post-déploiement sera annulée chaque fois que l'actualisation du schéma SSDT se produit (donc à tout le moins, notre base de code ne correspondra pas entre le contrôle de code source et ce qui se trouve réellement sur le serveur)
  4. L'ajout de la colonne comme nullable maintenant avec l'intention de passer à NOT NULL à l'avenir ne fonctionne pas sur plusieurs branches/fourches dans le contrôle de code source, car les systèmes cibles n'auront pas nécessairement tous la table dans le même état la prochaine fois qu'ils seront mis à niveau (pas que ce soit une bonne approche de toute façon OMI)

L'approche que j'ai entendue des autres est de mettre à jour directement la définition de la table (pour que l'actualisation du schéma soit cohérente), d'écrire un script de pré-déploiement qui déplace le contenu entier de la table vers une table temporaire avec la nouvelle logique de remplissage de colonne incluse, puis pour reculer les lignes dans un script de post-déploiement. Cela semble risqué comme tout l'enfer cependant, et fait toujours chier l'aperçu de publication lorsqu'il détecte qu'une colonne NOT NULL est ajoutée à une table avec des données existantes (car cette validation s'exécute avant le script de pré-déploiement).

Comment dois-je procéder pour ajouter une nouvelle colonne non nullable sans risquer de perdre des données orphelines ou déplacer des données dans les deux sens à chaque publication avec de longs scripts de migration qui sont intrinsèquement risqués?

Merci.

10
Elaskanator

Je vais partager comment j'ai fait cela dans le passé. Il est conçu pour résoudre la limitation spécifique des scripts de pré-déploiement que vous appelez dans votre deuxième point:

L'ajout de la colonne dans un script de pré-déploiement échouera la publication lorsqu'il essaiera automatiquement de créer la même colonne, une deuxième fois (même si le script de pré-déploiement est écrit pour être idempotent)

Pourquoi les scripts de pré-déploiement ne fonctionnent pas pour cela

Lorsque vous déployez un projet SSDT, la façon dont il assemble les choses est la suivante (un peu simplifiée, mais en général):

  1. Faire la "comparaison de schémas" entre la source (fichier dacpac) et la cible (base de données)
  2. Générez un script de déploiement basé sur les résultats de cette comparaison
  3. Traitez tous les scripts de pré-déploiement dans le dacpac (remplacement de jeton, etc.) et insérez le contenu au début du script de déploiement
  4. Faites de même pour les scripts de post-déploiement, en ajoutant à end du script de déploiement

Lorsqu'une nouvelle colonne existe dans le dacpac et non dans la base de données cible, l'étape # 2 générera du code pour ajouter cette colonne. Donc, si le script de pré-déploiement ajoute cette colonne, la partie principale du script échouera (car il suppose que la colonne n'existe pas, sur la base des résultats de la comparaison de schéma à l'étape # 1)

Solution: script pré-SSDT

Martin Smith a mentionné cette option dans un commentaire, et c'est la solution qui a le mieux fonctionné jusqu'à présent:

Nous utilisons des scripts prémodèles dans notre pipeline de déploiement. Ce n'est pas quelque chose qui fait partie de SSDT mais une étape qui s'exécute avant la publication de dacfx. Donc, dans ce cas, le script prémodèle pourrait ajouter la colonne avec les valeurs souhaitées et la rendre non nulle et au moment où la publication se produit, elle est déjà dans l'état attendu par SSDT, donc elle n'a rien à faire. Je n'ai pas encore trouvé beaucoup d'utilisation pour les scripts de pré-déploiement. - Martin Smith1 juin à 21h45

Les étapes pour implémenter cette solution en général sont:

  1. Créez un script dans le projet SSDT pour contenir votre code T-SQL "pré-SSDT"
    • Selon le fonctionnement de votre processus de déploiement, le code dans ces fichiers devrait probablement être idempotent
  2. Assurez-vous de définir ce script sur "Build Action = None" et "Copy to Output Directory = Copy always"
    • l'option "copier toujours" est particulièrement importante, car le processus de déploiement doit pouvoir trouver ce script dans vos artefacts de déploiement
  3. Dans votre processus de déploiement, recherchez et exécutez ce script (ou ces scripts) avant ​​la comparaison du schéma SSDT se produit
  4. Une fois que ce script a été exécuté avec succès, vous pouvez engager DacServices/DacFx/autre pour terminer votre déploiement comme d'habitude

En fin de compte, cela vous permet d'ajouter la colonne en utilisant le code personnalisé que vous aimez, rempli en utilisant une logique métier complexe, dans le script pré-SSDT.

Vous ajoutez également la définition de colonne dans le projet SSDT (afin que le contrôle des sources corresponde toujours à l'état réel de la base de données). Mais lorsque la comparaison de schémas s'exécute, il ne voit aucune modification liée à cette colonne (car vous l'avez déjà déployée).

Autres utilisations de la pré-SSDT

Je trouve souvent lors des tests de déploiements que SSDT effectue une opération de "reconstruction de table" * lorsque cela est complètement inutile. C'est là qu'une nouvelle table est créée avec le schéma mis à jour, toutes les données sont copiées dans cette table, l'ancienne table est supprimée et la nouvelle table est renommée pour remplacer l'ancienne table.

Cela peut entraîner une croissance du fichier journal des transactions massive et d'autres problèmes si le tableau est volumineux. Si je remarque qu'un changement de schéma est à l'origine de cela, je vais plutôt effectuer le changement moi-même en pré-SSDT (qui est généralement un simple ALTER TABLE) et éviter la reconstruction de la table.

Est-ce une bonne idée?

Je le pense. Si vous lisez Critiquer deux approches différentes pour fournir des bases de données: Migrations vs état par Alex Yates, cela combine essentiellement les deux approches un peu. SSDT est basé sur l'état, mais nous incorporons une étape de migration (avant SSDT) ​​pour gérer certains des scénarios les plus complexes que SSDT n'a tout simplement aucun moyen de traiter de manière générale.

En faisant des recherches tout en écrivant cette réponse, il s'agit en fait d'une approche très courante discutée dans la communauté des utilisateurs SSDT une fois que vous savez quoi rechercher. Je l'ai vu appelé:

  • pré-comparer
  • pré-modèle
  • pré-DAC
  • pré-SSDT

Etc. Voici un excellent article qui couvre un grand nombre des points que j'ai mentionnés ci-dessus:

Scripts de pré-comparaison et de pré-déploiement sur SSDT

Et un de Red Gate (dans la section # 4 - Passer du type de système au type défini par l'utilisateur) qui fait également référence à ceci comme pré-comparer:

Comment corriger dix accrochages de déploiement SSDT, avec ou sans ReadyRoll

Quel est donc l'intérêt des scripts de pré-déploiement?

Martin souligne qu'il n'a pas trouvé "beaucoup d'utilisation pour les scripts de pré-déploiement." J'ai tendance à ressentir la même chose. Mais il existe des scénarios où ils peuvent être utiles.

Par exemple, un collègue m'a signalé le stockage de certaines données dans une table temporaire à utiliser dans le script de post-déploiement (par exemple, vous déplacez une colonne d'une table à une autre).


* La reconstruction de la table ressemble à ceci, ce qui est horrible, non?

GO
PRINT N'Starting rebuilding table [dbo].[MyTable]...';


GO
BEGIN TRANSACTION;

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

SET XACT_ABORT ON;

CREATE TABLE [dbo].[tmp_ms_xx_MyTable] (
    [Id] BIGINT IDENTITY (1, 1) NOT NULL,
    -- etc, other columns
);

IF EXISTS (SELECT TOP 1 1 
           FROM   [dbo].[MyTable])
    BEGIN
        SET IDENTITY_INSERT [dbo].[tmp_ms_xx_MyTable] ON;
        INSERT INTO [dbo].[tmp_ms_xx_MyTable] ([Id], ...)
        SELECT   [Id],
                 -- etc, other columns
        FROM     [dbo].[MyTable]
        ORDER BY [Id] ASC;
        SET IDENTITY_INSERT [dbo].[tmp_ms_xx_MyTable] OFF;
    END

DROP TABLE [dbo].[MyTable];

EXECUTE sp_rename N'[dbo].[tmp_ms_xx_MyTable]', N'MyTable';

COMMIT TRANSACTION;

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
11
Josh Darnell