De maneira geral, você não pode emitir ALTER DATABASE
um acionador (ou qualquer transação que contenha outras instruções). Se você tentar, você receberá o seguinte erro:
Msg 226, Nível 16, Estado 6, Linha xxxx Instrução
ALTER DATABASE não permitida na transação de várias instruções.
A razão pela qual esse erro não foi encontrado na resposta do @ sp_BlitzErik é resultado do caso de teste específico fornecido: o erro mostrado acima é um erro em tempo de execução, enquanto o erro encontrado em sua resposta é um erro em tempo de compilação. Esse erro em tempo de compilação impede a execução do comando e, portanto, não há "tempo de execução". Podemos ver a diferença executando o seguinte:
SET NOEXEC ON;
SELECT N'g' COLLATE Latin1;
SET NOEXEC OFF;
O lote acima apresentará um erro, enquanto o seguinte não:
SET NOEXEC ON;
BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;
SET NOEXEC OFF;
Isso deixa você com duas opções:
Confirme a transação no gatilho DDL, para que não haja outras instruções na transação. Essa não é uma boa ideia se houver vários gatilhos DDL que podem ser disparados por uma CREATE DATABASE
instrução e possivelmente seja uma má ideia em geral, mas funciona ;-). O truque é que você também precisa iniciar uma nova transação no gatilho. O SQL Server observará que os valores inicial e final de @@TRANCOUNT
não coincidem e gerará um erro relacionado a isso. O código abaixo faz exatamente isso e também emite apenas ALTER
se o agrupamento não for o desejado, caso contrário, ele ignora o ALTER
comando.
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
Teste com:
-- 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];
Use SQLCLR para estabelecer um regular / externo SqlConnection
, Enlist = false;
na String de Conexão, para emitir o ALTER
comando, pois isso não fará parte da Transação.
Parece que o SQLCLR não é realmente uma opção, embora não seja devido a qualquer limitação específica do SQLCLR. De alguma forma, digitar " como isso não fará parte da transação " diretamente acima não destacou suficientemente o fato de que há, de fato, uma transação ativa em torno da CREATE DATABASE
operação. O problema aqui é que, embora o SQLCLR possa ser usado para sair da Transação atual, ainda não há como outra Sessão modificar o Banco de Dados que está sendo criado atualmente até que a Transação inicial seja confirmada.
Significado, a Sessão A cria a transação para a criação do banco de dados e o acionamento do gatilho. O Disparador, usando SQLCLR, criará a Sessão B para modificar o Banco de Dados que foi criado, mas a Transação ainda não foi confirmada, pois permanece em espera até a Sessão B ser concluída, o que não é possível porque está aguardando a transação inicial completo. Isso é um impasse, mas não pode ser detectado como tal pelo SQL Server, pois não sabe que a Sessão B foi criada por algo dentro da Sessão A. Esse comportamento pode ser visto substituindo a primeira parte da IF
instrução no exemplo acima em # 1 com o seguinte:
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
...
A -t 15
opção para SQLCMD define o tempo limite do comando / consulta para que o teste não espere um tempo para sempre com o tempo limite padrão. Mas você pode configurá-lo para mais de 15 segundos e, em outra sessão, verifique sys.dm_exec_requests
se todos os adoráveis bloqueios estão acontecendo ;-).
Enfileire o evento em algum lugar que lerá a partir dessa fila e executará a ALTER DATABASE
instrução apropriada . Isso permitirá que a CREATE DATABASE
instrução seja concluída e sua transação seja confirmada, após a qual uma ALTER DATABASE
instrução pode ser executada. O Service Broker pode ser usado aqui. OU, crie uma tabela, faça com que o Trigger seja inserido nessa tabela e faça com que um trabalho do SQL Server Agent chame um Procedimento Armazenado que leia essa tabela e execute a ALTER DATABASE
instrução e remova o registro da fila Tabela.
NO ENTANTO, as opções acima são fornecidas principalmente para ajudar em cenários em que alguém realmente precisa fazer algum tipo ALTER DATABASE
dentro de um gatilho DDL. Nesse cenário específico, se você realmente não deseja que nenhum banco de dados esteja usando o agrupamento padrão no nível do sistema / instância, provavelmente será melhor atendido por:
- Criando uma nova instância com o agrupamento desejado e movendo todos os bancos de dados do usuário para ele.
- Ou, se são apenas os bancos de dados do sistema que não são o agrupamento ideal, provavelmente é seguro alterar o agrupamento do sistema na linha de comando via setup.exe (por exemplo
Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...
, essa opção recria os bancos de dados do sistema, portanto, você precisará para criar scripts de objetos no nível do servidor, etc, para recriar mais tarde, além de reaplicar patches etc., FUN, FUN, FUN).
Ou, para os aventureiros de coração, existe a opção não documentada (isto é, sem suporte, use por seu próprio risco, mas pode funcionar muito bem) sqlservr.exe -q
que atualiza TODOS os DBs e TODAS as colunas (consulte Alteração o agrupamento da instância, os bancos de dados e todas as colunas em todos os bancos de dados do usuário: o que pode dar errado? para obter uma descrição detalhada do comportamento dessa opção e do escopo potencial do impacto).
Independentemente da opção escolhida: sempre faça backups master
e msdb
antes de tentar essas coisas.
A razão pela qual valeria a pena o esforço para alterar o agrupamento padrão no nível do servidor é que o agrupamento padrão da instância (ou seja, no nível do servidor) controla algumas áreas funcionais que podem levar a um comportamento inesperado / inconsistente, pois todos esperam que as operações de sequência funcionem ao longo das linhas do agrupamento padrão para todos os bancos de dados do usuário:
Agrupamento padrão para colunas de sequência em tabelas temporárias. Esse é um problema apenas ao comparar / Unioning com outras colunas de string SE houver uma incompatibilidade entre as duas colunas de string. O problema aqui é que, ao não especificar o agrupamento explicitamente por meio da COLLATE
palavra-chave, é muito mais provável (embora não garantido) que ocorra problemas.
Isso não é um problema para o tipo de dados XML, variáveis de tabela ou bancos de dados contidos.
Meta-dados no nível da instância. Por exemplo, o name
campo em sys.databases
usará o agrupamento padrão no nível da instância. Outras visualizações do catálogo do sistema também são afetadas, mas não tenho a lista completa.
Os metadados no nível do banco de dados, como sys.objects
e sys.indexes
, não são afetados.
- Resolução de nomes para:
- variáveis locais (ie
@variable
)
- cursores
GOTO
etiquetas
Por exemplo, se o agrupamento no nível da instância não diferencia maiúsculas de minúsculas, enquanto o agrupamento no nível do banco de dados é binário (ou seja, termina em _BIN
ou _BIN2
), a resolução de nomes de objetos no nível do banco de dados será binária (por exemplo [TableA] <> [tableA]
), mas os nomes de variáveis permitem a diferenciação de maiúsculas e minúsculas (por exemplo @VariableA = @variableA
).