SQL Serverデータベース更新スクリプトを作成しようとしています。テーブル内の列の存在をテストし、存在しない場合はデフォルト値で列を追加し、最後に同じテーブル内の別の列の現在の値に基づいてその列を更新します。このスクリプトを複数回実行できるようにしたいのですが、最初にテーブルを更新し、その後の実行でスクリプトを無視する必要があります。現在、私のスクリプトは次のようになっています。
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はエラー「無効な列名 'IsDownloadable'」を返します。つまり、列を更新する前にDDLをコミットする必要があります。さまざまな順列を試しましたが、どこでも高速になりません。
このスクリプトは、列が既に存在する場合を除き、正常に実行されません。列が既に存在している場合は、正確にdont必要です。
SQLスクリプトは、実行する前に解析する必要があります。スクリプトの解析時に列が存在しない場合、解析は失敗します。スクリプトが後で列を作成してもかまいません。パーサーはそれを知る方法がありません。
追加した列にアクセスする場合は、GO
ステートメント(バッチ区切り)を挿入する必要があります。ただし、それを行うと、以前のバッチの制御フローまたは変数を維持できなくなります。2つの別個のスクリプトを実行するようなものです。このため、条件付きでDDLとDMLの両方を同時に実行するのは困難です。
DMLはそれほど複雑ではないため、おそらく最もお勧めする回避策は、動的SQLを使用することです。動的SQLは、「実行時」まで構文解析を試みません。
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
私はしばしば自分でこの問題に悩まされていますが、残念ながら Aaronaught's answer で提案されている解決策は、@ parametersと 'strings'が関係しているとすぐに乱雑になります。ただし、同義語の使用法を利用することで、別の回避策を見つけました。
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
少なくともSQL Server 2008を使用している場合、列の追加時にWITH VALUES
を指定できます。これにより、既存のレコードにその属性のデフォルト値が設定されます。
IF COL_LENGTH('[dbo].[Trucks]', 'Is4WheelDrive') IS NULL
BEGIN
ALTER TABLE [dbo].[Trucks]
ADD [Is4WheelDrive] BIT NULL DEFAULT 1
WITH VALUES;
END
これにより、その列が存在しない場合、新しい列[Is4WheelDrive]
がテーブル[dbo].[Trucks]
に追加されます。新しい列を追加すると、既存のレコードにデフォルト値(この場合は1
のBIT値)が入力されます。列が既に存在する場合、レコードは変更されません。
受け入れられた答えは機能しますが、より複雑なケースでは、一時テーブルを使用してGOステートメントを超えてデータを保持できます。忘れずにクリーンアップしてください。
例えば:
-- 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