web-dev-qa-db-fra.com

Déclencher pour modifier la base de données sur la création

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.

9
Racer SQL

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:

  1. 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];
    
  2. 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 ;-).

  3. 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:

  1. Création d'une nouvelle instance avec la classement souhaitée et déplacer toutes vos bases de données utilisateur.
  2. Ou, si ce n'est que les bases de données système de la collation non idéale, il est probablement prudent de modifier la collation du système à partir de la ligne de commande via setup.exe (par exemple) (par exemple 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).
  3. 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:

  1. 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.

  2. 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.

  3. Nom Résolution pour:
    1. variables locales (c'est-à-dire @variable)
    2. curseurs
    3. GOTO étiquettes

Par 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).

8
Solomon Rutzky

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 .

11
Erik Darling

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 .

2
Henrico Bekker