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.
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
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
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