web-dev-qa-db-fra.com

La colonne de test existe, ajoute une colonne et met à jour une colonne

J'essaie d'écrire un script de mise à jour de la base de données SQL Server. Je souhaite tester l'existence d'une colonne dans une table, puis, si elle n'existe pas, ajoutez la colonne avec une valeur par défaut et mettez enfin à jour cette colonne en fonction de la valeur actuelle d'une colonne différente de la même table. Je veux que ce script puisse être exécuté plusieurs fois. La première mise à jour de la table et les exécutions suivantes, le script doit être ignoré. Mon script ressemble actuellement à ce qui suit:

IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_NAME = 'PurchaseOrder' AND COLUMN_NAME = 'IsDownloadable')
BEGIN

ALTER TABLE [dbo].[PurchaseOrder] ADD [IsDownloadable] bit NOT NULL DEFAULT 0

UPDATE [dbo].[PurchaseOrder] SET [IsDownloadable] = 1 WHERE [Ref] IS NOT NULL

END

SQL Server renvoie l'erreur "Nom de colonne non valide 'IsDownloadable'", c'est-à-dire que je dois valider le DDL avant de pouvoir mettre à jour la colonne. J'ai essayé diverses permutations mais je ne vais nulle part rapidement.

46
David Clarke

Ce script ne fonctionnera pas correctement à moins que la colonne existe déjà, ce qui correspond exactement au moment où vous n'avez pas n'en avez pas besoin.

Les scripts SQL doivent être analysés avant de pouvoir être exécutés. Si la colonne n'existe pas au moment de l'analyse du script, l'analyse échouera. Peu importe que vos scripts créent la colonne ultérieurement; l'analyseur n'a aucun moyen de le savoir.

Vous devez insérer une instruction GO (séparateur de lots) si vous souhaitez accéder à une colonne que vous venez d'ajouter. Cependant, une fois que vous avez fait cela, vous ne pouvez plus gérer aucun flux de contrôle ni aucune variable du lot précédent. C'est comme si vous exécutiez deux scripts distincts. Cela rend délicat de faire les deux DDL et DML, conditionnellement, en même temps.

La solution de contournement la plus simple, que je vous recommanderais probablement car votre DML n'est pas très complexe, consiste à utiliser du SQL dynamique, que l'analyseur ne tentera pas d'analyser avant le "runtime":

IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_NAME = 'PurchaseOrder' AND COLUMN_NAME = 'IsDownloadable')
BEGIN

    ALTER TABLE [dbo].[PurchaseOrder] ADD 
        [IsDownloadable] bit NOT NULL DEFAULT 0

    EXEC sp_executesql
        N'UPDATE [dbo].[PurchaseOrder] SET [IsDownloadable] = 1 WHERE [Ref] IS NOT NULL'

END
78
Aaronaught

J'ai moi-même souvent été ennuyé par ce problème, et malheureusement, la solution suggérée dans la réponse d'Aaronaught devient rapidement compliquée lorsque @parameters et 'strings' sont impliqués. Cependant, j'ai trouvé une solution de contournement différente en exploitant l'utilisation de synonymes:

IF(COL_LENGTH('MyTable', 'NewCol') IS NULL)
BEGIN
    ALTER TABLE MyTable ADD NewCol VARCHAR(16) NULL;

    CREATE SYNONYM hack FOR MyTable;
    UPDATE hack SET NewCol = 'Hello ' + OldCol;
    DROP SYNONYM hack;

    ALTER TABLE MyTable ALTER COLUMN NewCol VARCHAR(16) NOT NULL;
END
1
Ola Berntsson

Bien que la réponse acceptée fonctionne, pour un cas plus compliqué, vous pouvez utiliser une table temporaire pour conserver les données après l'instruction GO. assurez-vous simplement de le nettoyer après.

Par exemple:

-- Create a tempTable if it doesn't exist. Use a unique name here
IF OBJECT_ID('tempdb..#tempTable') IS NOT NULL DROP TABLE #tempTable
CREATE TABLE #tempTable (ColumnsCreated bit)

-- Create your new column if it doesn't exist. Also, insert into the tempTable.
IF NOT EXISTS (
    SELECT * FROM   INFORMATION_SCHEMA.COLUMNS 
    WHERE  TABLE_NAME = 'targetTable' AND COLUMN_NAME = 'newColumn')
BEGIN
    INSERT INTO #tempTable VALUES (1)

    ALTER TABLE .dbo.targetTable ADD newColumn [SMALLINT] NULL ;
END

GO

-- If the tempTable was inserted into, our new columns were created.
IF (EXISTS(SELECT * FROM #tempTable))
    BEGIN
    -- Do some data seeding or whatever
    END

-- Clean up - delete the tempTable.
IF OBJECT_ID('tempdb..#tempTable') IS NOT NULL DROP TABLE #tempTable
0
Jaked222