web-dev-qa-db-ja.com

作成時にデータベース照合を変更するトリガー

トリガーを作成して、作成時にデータベースの照合を変更しようとしていますが、トリガー内で使用するデータベース名をキャッチするにはどうすればよいですか?

USE master
GO
CREATE TRIGGER trg_DDL_ChangeCOllationDatabase
ON ALL SERVER
FOR CREATE_DATABASE
AS
declare @databasename varchar(200)
set @databasename =db_name()
    ALTER DATABASE @databasename COLLATE xxxxxxxxxxxxxxxxxxx
GO

明らかに、これは機能していません。

9
Racer SQL

一般的に言えば、トリガー(または他のステートメントを含むトランザクション)内でALTER DATABASEを発行することはできません。実行しようとすると、次のエラーが発生します。

メッセージ226、レベル16、状態6、行xxxx
ALTER DATABASEステートメントは、マルチステートメントトランザクション内では使用できません。

@ sp_BlitzErik's answer でこのエラーが発生しなかった理由は、提供された特定のテストケースの結果です。上記のエラーは実行時エラーですが、彼の回答で発生したエラーはコンパイルです-timeエラー。そのコンパイル時エラーはコマンドの実行を妨げるため、「実行時」はありません。次のコマンドを実行すると、違いを確認できます。

SET NOEXEC ON;

SELECT N'g' COLLATE Latin1;

SET NOEXEC OFF;

上記のバッチはエラーになりますが、次のエラーは発生しません。

SET NOEXEC ON;

BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;

SET NOEXEC OFF;

これには2つのオプションがあります。

  1. トランザクションに他のステートメントがないように、DDLトリガー内でトランザクションをコミットします。これは、CREATE DATABASEステートメントで起動できるDDLトリガーが複数ある場合はお勧めできません。一般的にはお勧めできませんが、機能します;-)。トリックは、トリガーでも新しいトランザクションを開始する必要があるということです。そうしないと、SQL Serverは@@TRANCOUNTの開始値と終了値が一致せず、それに関連するエラーをスローします。以下のコードはこれを行うだけであり、照合が必要なものでない場合にのみALTERを発行し、そうでない場合はALTERコマンドをスキップします。

    USE [master];
    GO
    CREATE TRIGGER trg_DDL_ChangeDatabaseCollation
    ON ALL SERVER
    FOR CREATE_DATABASE
    AS
    SET NOCOUNT ON;
    
    DECLARE @CollationName [sysname] = N'Latin1_General_100_BIN2',
            @SQL NVARCHAR(4000);
    
    SELECT @SQL = N'ALTER DATABASE ' + QUOTENAME(sd.[name]) + N' COLLATE ' + @CollationName
    FROM   sys.databases sd
    WHERE  sd.[name] = EVENTDATA().value(N'(/EVENT_INSTANCE/DatabaseName)[1]', N'sysname')
    AND    sd.[collation_name] <> @CollationName;
    
    IF (@SQL IS NOT NULL)
    BEGIN
      PRINT @SQL; -- DEBUG
      COMMIT TRAN; -- close existing Transaction, else will get error
      EXEC sys.sp_executesql @SQL;
      BEGIN TRAN; -- begin new Transaction, else will get different error
    END;
    ELSE
    BEGIN
      PRINT 'Collation already correct.';
    END;
    
    GO
    

    でテストする:

    -- skip ALTER:
    CREATE DATABASE [tttt] COLLATE Latin1_General_100_BIN2;
    DROP DATABASE [tttt];
    
    -- perform ALTER:
    CREATE DATABASE [tttt] COLLATE SQL_Latin1_General_CP1_CI_AI;
    DROP DATABASE [tttt];
    
  2. SQLCLRを使用して通常の/外部SqlConnectionを確立し、Enlist = false;を接続文字列に含めて、ALTERコマンドを発行します。これは、トランザクションの一部ではないためです。

    SQLCLRの特定の制限によるものではありませんが、SQLCLRは本当にオプションではないようです。どういうわけか、「それはトランザクションの一部ではないので」をすぐ上に入力しても、実際にCREATE DATABASEの周りにアクティブなトランザクションがあるという事実が十分に強調されませんでした。操作。ここでの問題は、SQLCLR can を使用して現在のトランザクションの外に出ることができる一方で、別のセッションが現在作成中のデータベースを変更する方法がないことです最初のトランザクションがコミットするまで

    つまり、セッションAは、データベースの作成とトリガーの起動のためのトランザクションを作成します。トリガーは、SQLCLRを使用してセッションBを作成し、作成されたデータベースを変更します。 but セッションBが完了するまでトランザクションは保留されているため、トランザクションはまだコミットされていません。その最初のトランザクションが完了するのを待っているからではありません。これはデッドロックですが、セッションBがセッションA内の何かによって作成されたことを認識していないため、SQL Serverによって検出されません。この動作は、IFステートメントは次のようになります。

    IF (@SQL IS NOT NULL)
    BEGIN
      /*
      PRINT @SQL; -- DEBUG
      COMMIT TRAN; -- close existing Transaction, else will get error
      EXEC sys.sp_executesql @sql;
      BEGIN TRAN; -- begin new Transaction, else will get different error
      */
      DECLARE @CMD NVARCHAR(MAX) = N'EXEC xp_cmdshell N''sqlcmd -S . -d master -E -Q "'
                                 + @SQL + N';" -t 15''';
      PRINT @CMD;
      EXEC (@CMD);
    END;
    ELSE
    ...
    

    [〜#〜] sqlcmd [〜#〜]-t 15スイッチは、テストが実行されないようにコマンド/クエリのタイムアウトを設定しますデフォルトのタイムアウトで永久に待機します。ただし、15秒より長く設定して、別のセッションでsys.dm_exec_requestsをチェックして、素敵なブロックがすべて行われていることを確認できます;-)。

  3. イベントをどこかにキューに入れ、そのキューから読み取り、適切なALTER DATABASEステートメントを実行します。これにより、CREATE DATABASEステートメントが完了し、そのトランザクションがコミットされます。その後、ALTER DATABASEステートメントを実行できます。ここではService Brokerを使用できます。または、テーブルを作成し、トリガーをそのテーブルに挿入してから、SQL Serverエージェントのジョブに、そのテーブルから読み取り、ALTER DATABASEステートメントを実行して、キューテーブルからレコードを削除するストアドプロシージャを呼び出します。

ただし、上記のオプションは主に、DDLトリガー内で実際にあるタイプのALTER DATABASEを実行する必要があるシナリオを支援するために提供されています。この特定のシナリオで、どのデータベースにもシステム/インスタンスレベルのデフォルトの照合順序を使用させたくない場合は、おそらく次の方法が最適です。

  1. 目的の照合で新しいインスタンスを作成し、すべてのユーザーデータベースをそのインスタンスに移動します。
  2. または、それが非理想的な照合順序のシステムデータベースだけである場合は、システムの照合順序を command-line via setup.exe から変更しても安全です(例:Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=... ;このオプションはシステムDBを再作成するため、後で再作成するためにサーバーレベルのオブジェクトなどをスクリプトで削除する必要があり、さらにパッチなどを再適用する必要があります(FUN、FUN、FUN)。
  3. または、冒険心のある人のために、すべてのDBとALLを更新する、文書化されていない(つまり、サポートされていない、自分で使用する-リスク-しかし、適切な作業を行う)sqlservr.exe -qオプションがあります。列(このオプションの動作の詳細な説明については、 [すべてのユーザーデータベースのインスタンス、データベース、およびすべての列の照合順序の変更:何が問題になるのでしょうか? を参照してください。潜在的な影響の範囲)。

    選択したオプションに関係なく: always をバックアップする前に、mastermsdbをバックアップしてください。

サーバーレベルのデフォルトの照合順序を変更するのに価値がある理由は、インスタンス(つまりサーバーレベル)のデフォルトの照合順序が、予期しない動作や一貫性のない動作につながる可能性があるいくつかの機能領域を制御するためです。すべてのユーザーデータベースのデフォルトの照合順序に沿って:

  1. 一時テーブルの文字列列のデフォルトの照合順序。これは、2つの文字列列の間に不一致がある場合に他の文字列列と比較/結合する場合にのみ問題になります。ここでの問題は、COLLATEキーワードを介して照合順序を明示的に指定しないと、問題が発生する可能性が非常に高くなります(保証はされません)。

    これは、XMLデータ型、テーブル変数、または包含データベースの問題ではありません。

  2. インスタンスレベルのメタデータ。たとえば、sys.databasesnameフィールドは、インスタンスレベルのデフォルトの照合順序を使用します。他のシステムカタログビューも影響を受けますが、完全なリストはありません。

    sys.objectssys.indexesなどのデータベースレベルのメタデータは影響を受けません。

  3. 名前解決:
    1. ローカル変数(つまり、@variable
    2. カーソル
    3. GOTOラベル

たとえば、インスタンスレベルの照合順序が大文字と小文字を区別せず、データベースレベルの照合順序がバイナリである(つまり、_BINまたは_BIN2で終わる)場合、データベースレベルのオブジェクト名解決はバイナリになります(たとえば、 [TableA] <> [tableA])ただし、変数名では大文字と小文字を区別しません(例:@VariableA = @variableA)。

8
Solomon Rutzky

動的SQLと EVENTDATA()関数 を使用する必要があります。

USE master
GO
CREATE TRIGGER trg_DDL_ChangeCOllationDatabase
ON ALL SERVER
FOR CREATE_DATABASE
AS
SET NOCOUNT ON; 
DECLARE @databasename NVARCHAR(256) = N''
DECLARE @event_data XML; 
DECLARE @sql NVARCHAR(4000) = N''

SET @event_data = EVENTDATA()

SET @databasename = @event_data.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(256)') 

SET @sql += 'ALTER DATABASE ' + QUOTENAME(@databasename) + ' COLLATE al''z a-b-cee''z'

PRINT @sql

EXEC sys.sp_executesql @sql

GO

私の 偽物 のためにあなたの照合にサブしてください。

データベースを作成すると...

CREATE DATABASE DingDong

(印刷物から)このメッセージが表示されます:

ALTER DATABASE [DingDong] COLLATE al'z a-b-cee'z

他のデータベース(tempdbを含む)が異なる照合順序を使用している場合、文字列データの比較で問題が発生する可能性があることに注意してください。大文字と小文字またはアクセントが重要な文字列比較にCOLLATE句を追加する必要があります。また、そうでない場合でも、エラーが発生する可能性があります。私が同様のコードの問題に遭遇した関連質問 here

11
Erik Darling

できませんALTER DATABASEトリガー内。あなたは評価と修正で創造的になる必要があります。何かのようなもの:

EXEC sp_MSforeachdb N'IF EXISTS 
(
     select top 1 name from sys.databases where collation_name != 
     SQL_Latin1_General_CP1_CI_AS
)
BEGIN
    -- do something
END';

あなたは sp_MSforeachdbを使用すべきではありません

2
Henrico Bekker