web-dev-qa-db-ja.com

テスト列が存在し、列を追加し、列を更新する

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をコミットする必要があります。さまざまな順列を試しましたが、どこでも高速になりません。

47
David Clarke

このスクリプトは、列が既に存在する場合を除き、正常に実行されません。列が既に存在している場合は、正確に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
79
Aaronaught

私はしばしば自分でこの問題に悩まされていますが、残念ながら 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
1
Ola Berntsson

少なくとも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値)が入力されます。列が既に存在する場合、レコードは変更されません。

0
srsedate

受け入れられた答えは機能しますが、より複雑なケースでは、一時テーブルを使用して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
0
Jaked222