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


9

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

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

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


1
MODELデータベースを必要な照合に変更できない理由はありますか?-新しく作成されたすべてのデータベースは、モデルとしてテンプレートを使用します
Scott Hodgin

試してみましたが、modelデータベースはシステムデータベースなので、変更できません。
レーサーSQL

したがって、システムデータベースは、ユーザーデータベースとは異なる照合順序になりますか?一時テーブルなどの潜在的な照合問題を検討しましたか?
George.Palacios、2017年

うわぁ、5分前くらいに読んだよ。そのことを考えていませんでした。それは良い考えではありません。
レーサーSQL

回答:


8

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

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

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

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トリガーが複数ある場合はお勧めできません。一般的にはお勧めできませんが、機能します;-)。トリックは、トリガーでも新しいトランザクションを開始する必要があることです。そう@@TRANCOUNTしないと、SQL Serverはの開始値と終了値が一致しないことに気づき、それに関連するエラーをスローします。以下のコードはこれを実行するだけ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 使用して現在のトランザクションの外に出ることできる一方で、その最初のトランザクションがコミットするまで、別のセッションが現在作成中のデータベースを変更する方法がないことです。

    つまり、セッションAは、データベースの作成とトリガーの起動のためのトランザクションを作成します。トリガーは、SQLCLRを使用してセッションBを作成し、作成されたデータベースを変更します、トランザクションは、セッションBが完了するまで保留されているため、まだコミットされていません。コンプリート。これはデッドロックですが、セッションBがセッションA内の何かによって作成されたことを認識していないため、SQL Serverによって検出することはできません。この動作はIF、例のステートメントの最初の部分を置き換えることで確認できます上記の#1で、次のとおりです。

    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ステートメントを実行して、キューテーブルからレコードを削除するストアドプロシージャを呼び出します。

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

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

    かかわらず、オプションの選択:いつものバックアップ持っていることを確認してくださいmastermsdb、このような事を試みる前に。

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

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

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

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

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

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

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


11

動的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 ab-cee'z

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


1
@RafaelPiccinelliとErik:参考までに、この答えは完全に正しいわけではありません。コードは機能しませんが、テストが無効な照合名を使用しているため、実際のエラーはマスクされます。コメントが多すぎるため、説明を(上に向かって)説明するために回答を更新しました。
ソロモンルツキー

2

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を使用しないでください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.