Pemicu untuk mengubah susunan Database saat pembuatan

9

Saya mencoba membuat Pemicu, untuk mengubah susunan database pada pembuatannya, tetapi bagaimana saya bisa menangkap nama database untuk digunakan di dalam pelatuk?

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

Jelas, ini tidak berhasil.

SQL pembalap
sumber
1
Apakah ada alasan mengapa Anda tidak bisa begitu saja mengubah basis data MODEL ke susunan yang Anda butuhkan? - semua database yang baru dibuat akan menggunakan MODEL sebagai templat
Scott Hodgin
Saya mencobanya tetapi dikatakan model database adalah database sistem, jadi saya tidak bisa mengubahnya.
Pembalap SQL
Jadi basis data sistem Anda akan berada dalam susunan yang berbeda dengan db pengguna Anda? Sudahkah Anda mempertimbangkan masalah pengumpulan potensial dengan tabel temp dll?
George.Palacios
Wow, Ya, saya membacanya 5 menit yang lalu. Tidak memikirkan itu. Itu bukan Ide yang bagus.
Pembalap SQL

Jawaban:

8

Anda tidak dapat, secara umum, mengeluarkan ALTER DATABASEdalam Pemicu (atau Transaksi apa pun yang memiliki pernyataan lain di dalamnya). Jika Anda berusaha, Anda akan mendapatkan kesalahan berikut:

Msg 226, Level 16, Negara 6, Baris xxxx
ALTER DATABASE pernyataan tidak diizinkan dalam transaksi multi-pernyataan.

Alasan bahwa kesalahan ini tidak ditemukan dalam jawaban @ sp_BlitzErik adalah hasil dari kasus uji khusus yang disediakan: kesalahan yang ditunjukkan di atas adalah kesalahan run-time, sedangkan kesalahan yang ditemukan dalam jawabannya adalah kesalahan waktu kompilasi. Kesalahan waktu kompilasi mencegah eksekusi perintah dan karenanya tidak ada "run-time". Kita dapat melihat perbedaannya dengan menjalankan yang berikut ini:

SET NOEXEC ON;

SELECT N'g' COLLATE Latin1;

SET NOEXEC OFF;

Batch di atas akan error, sedangkan yang berikut tidak akan:

SET NOEXEC ON;

BEGIN TRAN
CREATE TABLE #t (Col1 INT);
ALTER DATABASE CURRENT COLLATE Latin1_General_100_BIN2;
ROLLBACK TRAN;

SET NOEXEC OFF;

Ini memberi Anda dua opsi:

  1. Melakukan Transaksi dalam Pemicu DDL sedemikian rupa sehingga tidak ada pernyataan lain dalam Transaksi. Ini bukan ide yang baik jika ada beberapa Pemicu DDL yang dapat dipecat oleh CREATE DATABASEpernyataan, dan mungkin ide yang buruk secara umum, tetapi itu berhasil ;-). Kuncinya adalah bahwa Anda juga perlu memulai Transaksi baru di Trigger SQL Server yang lain akan melihat bahwa nilai awal dan akhir untuk @@TRANCOUNTtidak cocok dan akan menimbulkan kesalahan terkait dengan itu. Kode di bawah tidak hanya ini, dan juga hanya mengeluarkan ALTERjika Collation bukan yang diinginkan, jika tidak maka lompatan ALTERperintah.

    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
    

    Uji dengan:

    -- 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. Gunakan SQLCLR untuk membuat biasa / eksternal SqlConnection, dengan Enlist = false;di Connection Connection, untuk mengeluarkan ALTERperintah karena itu tidak akan menjadi bagian dari Transaksi.

    Tampaknya SQLCLR bukan benar-benar pilihan, meskipun bukan karena batasan spesifik SQLCLR. Entah bagaimana, mengetik " karena itu tidak akan menjadi bagian dari Transaksi " langsung di atas tidak cukup menyoroti fakta bahwa, pada kenyataannya, ada Transaksi aktif di sekitar CREATE DATABASEoperasi. Masalahnya di sini adalah bahwa sementara SQLCLR dapat digunakan untuk melangkah keluar dari Transaksi saat ini, masih belum ada cara bagi Sesi lain untuk memodifikasi Database yang saat ini sedang dibuat sampai Transaksi awal tersebut dilakukan.

    Artinya, Sesi A menciptakan Transaksi untuk pembuatan Database dan penembakan Pemicu. Pemicu, menggunakan SQLCLR, akan membuat Sesi B untuk memodifikasi Database yang telah dibuat, tetapi Transaksi belum dilakukan sejak ditahan sampai Sesi B selesai, yang tidak bisa karena menunggu Transaksi awal untuk lengkap. Ini adalah jalan buntu, tetapi tidak dapat dideteksi seperti itu oleh SQL Server karena tidak tahu bahwa Sesi B dibuat oleh sesuatu dalam Sesi A. Perilaku ini dapat dilihat dengan mengganti bagian pertama dari IFpernyataan dalam contoh. di atas dalam # 1 dengan yang berikut:

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

    The -t 15saklar untuk SQLCMD set perintah / permintaan batas waktu sehingga tes tidak menunggu selamanya dengan batas waktu default. Tapi, Anda bisa mengaturnya menjadi lebih dari 15 detik dan di sesi lain periksa sys.dm_exec_requestsuntuk melihat semua pemblokiran yang indah terjadi ;-).

  3. Antri acara di suatu tempat yang kemudian akan membaca dari antrian itu dan jalankan ALTER DATABASEpernyataan yang sesuai . Ini akan memungkinkan untuk menyelesaikan CREATE DATABASEpernyataan dan transaksinya, setelah ALTER DATABASEpernyataan dapat dieksekusi. Broker Layanan dapat digunakan di sini. ATAU, buat tabel, minta Pemicu menyisipkan ke dalam tabel itu, lalu minta pekerjaan SQL Server Agent memanggil Prosedur Tersimpan yang membaca dari tabel itu dan menjalankan ALTER DATABASEpernyataan dan kemudian menghapus catatan dari Tabel antrian.

NAMUN, opsi di atas terutama disediakan untuk membantu dalam skenario di mana seseorang benar-benar perlu melakukan beberapa jenis ALTER DATABASEdalam Pemicu DDL. Dalam skenario khusus ini, jika Anda benar-benar tidak ingin database menggunakan sistem / Collation default tingkat Instance, maka Anda mungkin akan dilayani oleh:

  1. Membuat instance baru dengan Collation yang diinginkan dan memindahkan semua Database pengguna Anda ke sana.
  2. Atau, jika itu hanya database sistem yang dari Collation non-ideal, mungkin aman untuk mengubah sistem Collation dari baris perintah melalui setup.exe (misalnya Setup.exe /Q /ACTION=Rebuilddatabase /INSTANCENAME=<instancename> /SQLCOLLATION=...; opsi ini menciptakan kembali sistem DB, jadi Anda akan perlu untuk skrip keluar objek tingkat server, dll untuk membuat ulang nanti, ditambah menerapkan kembali tambalan, dll, MENYENANGKAN, MENYENANGKAN, MENYENANGKAN).
  3. Atau, untuk para petualang, ada opsi yang tidak terdokumentasi (yaitu tidak didukung, digunakan dengan risiko Anda sendiri tetapi beresiko sangat baik) sqlservr.exe -qyang memperbarui SEMUA DB dan kolom ALL (silakan lihat Mengubah Kolaborasi Instance, Basis Data, dan Semua Kolom di Semua Basis Data Pengguna: Apa yang Mungkin Salah? untuk deskripsi terperinci tentang perilaku opsi ini, serta potensi lingkup dampak).

    Terlepas dari opsi yang dipilih: selalu pastikan untuk memiliki cadangan masterdan msdbsebelum mencoba hal-hal seperti itu.

Alasan yang sepadan dengan upaya untuk mengubah Collation default tingkat Server adalah bahwa Collation default Instance (yaitu Server-level) mengontrol beberapa area fungsional yang dapat menyebabkan perilaku tak terduga / tidak konsisten adalah semua orang mengharapkan operasi string berfungsi sepanjang garis Collation default untuk semua Database Pengguna Anda:

  1. Kolasi Default untuk kolom string dalam tabel sementara. Ini adalah masalah hanya ketika membandingkan / Menyatukan dengan kolom string lain JIKA ada ketidakcocokan antara dua kolom string. Masalahnya di sini adalah bahwa ketika tidak menentukan Kolasi secara eksplisit melalui COLLATEkata kunci, kemungkinan besar (meskipun tidak dijamin) akan mengalami masalah.

    Ini bukan masalah untuk tipe data XML, variabel tabel, atau Database yang Terkandung.

  2. Meta-data tingkat instan. Misalnya, namebidang dalam sys.databasesakan menggunakan Kolasi default tingkat Instance. Tampilan katalog sistem lain juga terpengaruh, tetapi saya tidak memiliki daftar lengkap.

    Meta-data tingkat basis data, seperti sys.objectsdan sys.indexes, tidak terpengaruh.

  3. Resolusi nama untuk:
    1. variabel lokal (yaitu @variable)
    2. kursor
    3. GOTO label

Misalnya, jika Instance-level Collation adalah case-insensitive sedangkan Collation-level Database adalah biner (yaitu diakhiri dengan _BINatau _BIN2), maka resolusi nama objek level-Database akan menjadi biner (misalnya [TableA] <> [tableA]) namun nama variabel akan memungkinkan untuk case-insensitivity (misalnya @VariableA = @variableA).

Solomon Rutzky
sumber
11

Anda perlu menggunakan SQL dinamis, dan fungsi 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

Cukup masukkan dalam koleksimu untuk yang palsu .

Sekarang ketika saya membuat database ...

CREATE DATABASE DingDong

Saya mendapatkan pesan ini (dari cetakan):

ALTER DATABASE [DingDong] KUMPULKAN al'z ab-cee'z

Perhatikan bahwa jika basis data lain (termasuk tempdb) menggunakan kumpulan yang berbeda, Anda dapat mengalami masalah ketika membandingkan data string. Anda harus menambahkan klausa COLLATE ke perbandingan string di mana casing atau aksen penting, dan bahkan ketika mereka tidak Anda dapat menekan kesalahan. Pertanyaan terkait di mana saya mengalami masalah kode serupa di sini .

Erik Darling
sumber
1
@ RafaelPiccinelli dan Erik: hanya FYI, jawaban ini tidak sepenuhnya benar. Kode tidak berfungsi, tetapi kesalahan sebenarnya ditutupi karena pengujian menggunakan nama Collation yang tidak valid. Saya memperbarui jawaban saya untuk menjelaskan (ke atas) karena terlalu banyak untuk komentar.
Solomon Rutzky
2

Anda tidak bisa ALTER DATABASEdalam pemicu. Anda harus kreatif dengan evaluasi dan koreksi. Sesuatu seperti:

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';

Meskipun Anda tidak harus menggunakan sp_MSforeachdb .

Henrico Bekker
sumber