J'essaie de créer un déclencheur, de modifier la comparaison d'une base de données sur sa création, mais comment puis-je attraper le nom de la base de données à utiliser à l'intérieur de la gâchette?
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
De toute évidence, cela ne fonctionne pas.
Vous ne pouvez généralement pas, en général, émettez ALTER DATABASE
dans un déclencheur (ou toute transaction qui a d'autres affirmations de celui-ci). Si vous essayez de, vous obtiendrez l'erreur suivante:
Msg 226, niveau 16, état 6, ligne xxxx
[.____] modifier la déclaration de base de données non autorisée dans la transaction multi-instruction.
La raison que cette erreur n'a pas été rencontrée dans @ Réponse de SP_blitzerik résulte du cas de test spécifique fourni: L'erreur indiquée ci-dessus est une erreur d'exécution, tandis que l'erreur rencontrée dans Sa réponse est une erreur de compilation. Cette erreur de compilation empêche l'exécution de la commande et qu'il n'y a donc pas de "temps d'exécution". Nous pouvons voir la différence en exécutant ce qui suit:
SET NOEXEC ON;
SELECT N'g' COLLATE Latin1;
SET NOEXEC OFF;
Le lot ci-dessus sera une erreur, tandis que les éléments suivants ne seront pas:
SET NOEXEC ON;
BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;
SET NOEXEC OFF;
Cela vous laisse avec deux options:
Commettre la transaction dans la gâchette DDL de manière à ce qu'il n'y ait pas d'autres déclarations de la transaction. Ce n'est pas une bonne idée s'il y a plusieurs déclencheurs DDL pouvant être tirés par une déclaration CREATE DATABASE
et constitue peut-être une mauvaise idée en général, mais cela fonctionne ;-). L'astuce est que vous devez également commencer une nouvelle transaction dans la gâchette SQL Server remarquera que les valeurs de début et de fin de @@TRANCOUNT
ne correspondent pas et jettent une erreur liée à cela. Le code ci-dessous ne fait que cela, et seulement uniquement le ALTER
si la classement n'est pas souhaité, sinon elle saute la commande 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
Test avec:
-- 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];
Utilisez SQLCLR pour établir un SqlConnection
, avec Enlist = false;
dans la chaîne de connexion, pour émettre la commande ALTER
comme cela ne fera pas partie de la transaction.
Il semble que SQLCLR n'est pas vraiment une option, mais pas en raison d'une limitation spécifique de SQLCLR. En quelque sorte, en tapant " comme ça ne fera pas partie de la transaction" Directement ci-dessus n'a pas suffisamment souligné le fait qu'il ya, en fait, une transaction active autour de l'opération CREATE DATABASE
. Le problème ici est que, tandis que SQLCLR peut être utilisé pour aller à l'extérieur de la transaction en cours, il n'existe toujours aucun moyen pour une autre session de modifier la base de données actuellement créée jusqu'à que la transaction initiale s'engage.
Signification, la session A crée la transaction pour la création de la base de données et la cuisson de la gâchette. La gâchette, à l'aide de SQLCLR, créera une session B pour modifier la base de données créée, mais La transaction n'est pas encore engagée car elle est en attente jusqu'à ce que la session B complète, ce qu'elle ne peut pas parce qu'il attend que la transaction initiale complète. C'est une impasse, mais elle ne peut pas être détectée comme telle par SQL Server car elle ne sait pas que la session B a été créée par quelque chose au sein de la session A. Ce comportement peut être vu en remplaçant la première partie du IF
Déclaration dans l'exemple ci-dessus dans la n ° 1 avec ce qui suit:
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
...
Le commutateur -t 15
pour SQLCMD Définit le délai de commande/requête afin que le test n'attend pas pour toujours avec le délai d'attente par défaut. Mais, vous pouvez la définir pour être plus long que 15 secondes et dans un autre chèque de session sys.dm_exec_requests
pour voir tout le charmant blocage de la vie ;-).
Queue l'événement quelque part qui va ensuite lire à partir de cette file d'attente et exécuter la déclaration de ALTER DATABASE
appropriée. Cela permettra à la déclaration CREATE DATABASE
de compléter et de sa transaction pour commettre, après quoi une instruction ALTER DATABASE
peut être exécutée. Le courtier de service pourrait être utilisé ici. Ou, créez une table, avez l'insertion de la gâchette dans cette table, puis avez un appel d'emploi SQL Server Agent une procédure stockée à partir de ce tableau et exécute l'instruction ALTER DATABASE
, puis supprime l'enregistrement de la table de file d'attente.
Cependant, les options ci-dessus sont principalement fournies pour aider à des scénarios où quelqu'un doit vraiment faire un certain type de ALTER DATABASE
dans un déclencheur DDL. Dans ce scénario particulier, si vous ne voulez vraiment pas que les bases de données utilisent la couverture par défaut du système/de l'instance, vous serez probablement mieux servi par:
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...
; cette option recrée la DBS du système, vous devrez donc scripter des objets au niveau du serveur, etc., pour recréer plus tard, plus les correctifs, etc., amusant, amusant, amusant).Ou, pour l'aventurier-at-coeur, il y a le non-documenté (c'est-à-dire non pris en charge, à usage-user-user-work-work-work-work) sqlservr.exe -q
option qui met à jour tous les DBS et toutes les colonnes (s'il vous plaît voir Modification de la classement de l'instance, des bases de données et de toutes les colonnes de toutes les bases de données utilisateur: Qu'est-ce qui pourrait éventuellement aller mal? Pour une description détaillée du comportement de cette option, ainsi que la portée potentielle de l'impact).
Quelle que soit l'option choisie: TOUJOURS Assurez-vous d'avoir des sauvegardes de master
et msdb
avant de tenter de telles choses.
La raison pour laquelle il valait la peine de modifier la comparaison par défaut du niveau du serveur est que la collation par défaut de l'instance (c.-à-d. Le niveau de serveur) contrôle quelques zones fonctionnelles pouvant entraîner un comportement inattendu/incohérent est que tout le monde s'attend à ce que les opérations de chaîne soient fonctionner. Dans le cadre des lignes de la comparaison par défaut pour toutes vos bases de données utilisateur:
Collation par défaut pour les colonnes de chaîne dans des tables temporaires. Ceci est un problème uniquement lors de la comparaison de/syndicalisation avec d'autres colonnes à chaîne s'il existe une inadéquation entre les deux colonnes de chaîne. Le problème ici est que, lorsqu'il ne spécifie pas la classement explicitement via le mot-clé COLLATE
, il est beaucoup plus probable (bien que non garanti) de rencontrer des problèmes.
Ce n'est pas un problème pour le type XML DataType, les variables de table ou les bases de données contenues.
Méta-données de niveau d'instance. Par exemple, le champ name
dans sys.databases
utilisera la collation par défaut au niveau de l'instance. Les autres vues de catalogue de systèmes sont également affectées, mais je n'ai pas la liste complète.
Les métadonnées de niveau de la base de données, telles que sys.objects
et sys.indexes
, ne sont pas affectées.
@variable
)GOTO
étiquettesPar exemple, si la collation de niveau d'instance est insensible à la casse tandis que la collation de niveau de la base de données est binaire (c.-à-d. Sous la fin de _BIN
ou _BIN2
), la résolution du nom d'objet de niveau de la base de données sera binaire (par exemple, [TableA] <> [tableA]
) et les noms de variables permettront de insensivité de cas (par exemple @VariableA = @variableA
).
Vous auriez besoin d'utiliser SQL dynamique et la fonction 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
Il suffit de submerger votre collision pour mon faux .
Maintenant, quand je crée une base de données ...
CREATE DATABASE DingDong
Je reçois ce message (de l'impression):
Alter base de données [Dingdong] Collate Al'Z A-B-CEE'Z
Notez simplement que si d'autres bases de données (y compris TEMPDB) utilisent différentes collations, vous pouvez rencontrer des problèmes comparant les données de chaîne. Vous devrez ajouter des clauses d'assemblage à des comparaisons de cordes où les boîtiers ou les accents sont importants, et même quand ils ne peuvent pas avoir des erreurs. Question liée où j'ai rencontré un problème de code similaire ici .
Vous ne pouvez pas ALTER DATABASE
dans un déclencheur. Vous devrez devenir créatif avec l'évaluation et la correction. Quelque chose comme:
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';
Bien que vous ne devrait pas utiliser sp_msforeachdb .