トリガーを作成して、作成時にデータベースの照合を変更しようとしていますが、トリガー内で使用するデータベース名をキャッチするにはどうすればよいですか?
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
明らかに、これは機能していません。
一般的に言えば、トリガー(または他のステートメントを含むトランザクション)内で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つのオプションがあります。
トランザクションに他のステートメントがないように、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];
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
をチェックして、素敵なブロックがすべて行われていることを確認できます;-)。
イベントをどこかにキューに入れ、そのキューから読み取り、適切なALTER DATABASE
ステートメントを実行します。これにより、CREATE DATABASE
ステートメントが完了し、そのトランザクションがコミットされます。その後、ALTER DATABASE
ステートメントを実行できます。ここではService Brokerを使用できます。または、テーブルを作成し、トリガーをそのテーブルに挿入してから、SQL Serverエージェントのジョブに、そのテーブルから読み取り、ALTER DATABASE
ステートメントを実行して、キューテーブルからレコードを削除するストアドプロシージャを呼び出します。
ただし、上記のオプションは主に、DDLトリガー内で実際にあるタイプのALTER DATABASE
を実行する必要があるシナリオを支援するために提供されています。この特定のシナリオで、どのデータベースにもシステム/インスタンスレベルのデフォルトの照合順序を使用させたくない場合は、おそらく次の方法が最適です。
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...
;このオプションはシステムDBを再作成するため、後で再作成するためにサーバーレベルのオブジェクトなどをスクリプトで削除する必要があり、さらにパッチなどを再適用する必要があります(FUN、FUN、FUN)。または、冒険心のある人のために、すべてのDBとALLを更新する、文書化されていない(つまり、サポートされていない、自分で使用する-リスク-しかし、適切な作業を行う)sqlservr.exe -q
オプションがあります。列(このオプションの動作の詳細な説明については、 [すべてのユーザーデータベースのインスタンス、データベース、およびすべての列の照合順序の変更:何が問題になるのでしょうか? を参照してください。潜在的な影響の範囲)。
選択したオプションに関係なく: always をバックアップする前に、master
とmsdb
をバックアップしてください。
サーバーレベルのデフォルトの照合順序を変更するのに価値がある理由は、インスタンス(つまりサーバーレベル)のデフォルトの照合順序が、予期しない動作や一貫性のない動作につながる可能性があるいくつかの機能領域を制御するためです。すべてのユーザーデータベースのデフォルトの照合順序に沿って:
一時テーブルの文字列列のデフォルトの照合順序。これは、2つの文字列列の間に不一致がある場合に他の文字列列と比較/結合する場合にのみ問題になります。ここでの問題は、COLLATE
キーワードを介して照合順序を明示的に指定しないと、問題が発生する可能性が非常に高くなります(保証はされません)。
これは、XMLデータ型、テーブル変数、または包含データベースの問題ではありません。
インスタンスレベルのメタデータ。たとえば、sys.databases
のname
フィールドは、インスタンスレベルのデフォルトの照合順序を使用します。他のシステムカタログビューも影響を受けますが、完全なリストはありません。
sys.objects
やsys.indexes
などのデータベースレベルのメタデータは影響を受けません。
@variable
)GOTO
ラベルたとえば、インスタンスレベルの照合順序が大文字と小文字を区別せず、データベースレベルの照合順序がバイナリである(つまり、_BIN
または_BIN2
で終わる)場合、データベースレベルのオブジェクト名解決はバイナリになります(たとえば、 [TableA] <> [tableA]
)ただし、変数名では大文字と小文字を区別しません(例:@VariableA = @variableA
)。
動的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 。
できません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を使用すべきではありません 。