Mengubah penggunaan GETDATE () di seluruh basis data

27

Saya perlu memigrasi database SQL Server 2017 lokal ke database Azure SQL, dan saya menghadapi beberapa tantangan karena ada sedikit batasan yang harus dilalui.

Secara khusus, karena database Azure SQL hanya berfungsi dalam waktu UTC (tanpa zona waktu) dan kami membutuhkan waktu lokal, kami harus mengubah penggunaan di GETDATE() mana - mana dalam database, yang telah terbukti lebih berfungsi daripada yang saya perkirakan.

Saya membuat fungsi yang ditentukan pengguna untuk mendapatkan waktu lokal yang berfungsi dengan benar untuk zona waktu saya:

CREATE FUNCTION [dbo].[getlocaldate]()
RETURNS datetime
AS
BEGIN
    DECLARE @D datetimeoffset;
    SET @D = CONVERT(datetimeoffset, SYSDATETIMEOFFSET()) AT TIME ZONE 'Pacific SA Standard Time';
    RETURN(CONVERT(datetime,@D));
END

Masalah yang saya alami masalah adalah untuk benar-benar berubah GETDATE()dengan fungsi ini di setiap tampilan, prosedur tersimpan, kolom yang dihitung, nilai default, kendala lain, dll.

Apa cara terbaik untuk menerapkan perubahan ini?

Kami berada di pratinjau publik dari Managed Instances . Masalahnya masih sama GETDATE(), jadi tidak membantu dengan masalah ini. Pindah ke Azure adalah persyaratan. Basis data ini digunakan (dan akan digunakan) selalu di zona waktu ini.

Lamak
sumber

Jawaban:

17
  1. Gunakan alat SQL Server untuk mengekspor definisi objek database ke file SQL yang harus mencakup: tabel, tampilan, pemicu, SP, fungsi, dan sebagainya

  2. Edit file SQL (buat cadangan terlebih dahulu) menggunakan editor teks apa pun yang memungkinkan Anda menemukan teks "GETDATE()"dan menggantinya dengan"[dbo].[getlocaldate]()"

  3. Jalankan file SQL yang diedit dalam Azure SQL untuk membuat objek basis data Anda ...

  4. Jalankan migrasi data.

Di sini Anda memiliki referensi dari dokumentasi azure: Membuat Skrip untuk SQL Azure

AMG
sumber
Meskipun dalam praktiknya pendekatan ini lebih rumit daripada kedengarannya, itu mungkin jawaban yang benar dan terbaik. Saya harus melakukan tugas yang mirip dengan ini berkali-kali dan saya sudah mencoba setiap pendekatan yang tersedia dan saya belum menemukan yang lebih baik (atau bahkan dekat, sungguh). pendekatan lain tampak hebat pada awalnya, tetapi mereka dengan cepat menjadi mimpi buruk pengawasan dan gotcha.
RBarryYoung
15

Apa cara terbaik untuk menerapkan perubahan ini?

Saya akan bekerja sebaliknya. Konversikan semua cap waktu Anda di basis data menjadi UTC, dan cukup gunakan UTC dan ikuti arus. Jika Anda memerlukan cap waktu di tz berbeda, Anda dapat membuat kolom yang dibuat menggunakan AT TIME ZONE(seperti yang Anda lakukan di atas) yang merender cap waktu di TZ yang ditentukan (untuk aplikasi). Tetapi, saya akan dengan serius mempertimbangkan untuk mengembalikan UTC ke aplikasi, dan menulis logika itu - logika tampilan - di aplikasi.

Evan Carroll
sumber
jika itu hanya masalah basis data, saya dapat mempertimbangkan ini, tetapi perubahan itu memengaruhi banyak aplikasi dan perangkat lunak lain yang memerlukan refactoring serius. Jadi, sayangnya, itu bukan pilihan bagi saya
Lamak
5
Apa jaminan yang akan Anda miliki bahwa tidak ada "aplikasi dan perangkat lunak" yang menggunakan getdate ()? yaitu kode sql yang tertanam dalam aplikasi. Jika Anda tidak dapat menjamin itu, refactoring database untuk menggunakan fungsi yang berbeda hanya akan mengarah pada inkonsistensi.
Tuan Magoo
@MisterMagoo Tergantung pada praktik di toko, terus terang saya pikir ini adalah masalah yang sangat kecil, dan saya tidak bisa melihatnya mengambil banyak waktu untuk mengajukan pertanyaan untuk menyelesaikan masalah kemudian untuk benar-benar memperbaiki saya. Akan menarik jika pertanyaan ini bukan Azure, karena saya bisa meretasnya dan memberi Anda lebih banyak umpan balik. Hal-hal cloud menyebalkan: mereka tidak mendukungnya, jadi Anda harus mengubah sesuatu di sisi Anda. Saya lebih suka menempuh rute yang disediakan dalam jawaban saya dan menghabiskan waktu untuk melakukannya dengan benar. Juga, Anda tidak memiliki jaminan apa pun akan berfungsi ketika Anda pindah ke Azure, seperti biasa tias.
Evan Carroll
@EvanCarroll, maaf saya baru saja membaca kembali komentar saya dan saya tidak menyatakan maksud saya dengan baik! Saya bermaksud mendukung jawaban Anda (terunggah) dan menyampaikan pendapat bahwa saran untuk hanya mengubah penggunaan getdate () menjadi getlocaldate () dalam basis data akan membuat mereka terbuka terhadap inkonsistensi dari sisi aplikasi, dan terlebih lagi hanya sebuah menempel plester pada masalah yang lebih besar. 100% setuju dengan jawaban Anda, memperbaiki masalah inti adalah pendekatan yang tepat.
Tuan Magoo
@MisterMagoo Saya memahami kekhawatiran Anda, tetapi dalam hal ini, saya dapat menjamin bahwa aplikasi dan perangkat lunak berinteraksi dengan database hanya melalui prosedur tersimpan
Lamak
6

Daripada mengekspor, mengedit secara manual, dan menjalankannya kembali, Anda dapat mencoba melakukan pekerjaan secara langsung dalam database dengan sesuatu seperti:

DECLARE C CURSOR FOR
        SELECT sm.definition, so.type
        FROM   sys.objects so
        JOIN   sys.all_sql_modules sm ON sm.object_id = so.object_id
        WHERE  so.type IN ('P', 'V')
        ORDER BY so.name
DECLARE @SQL NVARCHAR(MAX), @ojtype NVARCHAR(MAX)
OPEN C
FETCH NEXT FROM C INTO @SQL, @ojtype
WHILE @@FETCH_STATUS = 0 BEGIN
    IF @objtype = 'P' SET @SQL = REPLACE(@SQL, 'CREATE PROCEDURE', 'ALTER PROCEDURE') 
    IF @objtype = 'V' SET @SQL = REPLACE(@SQL, 'CREATE VIEW'     , 'ALTER VIEW'     ) 
    SET @SQL = REPLACE(@SQL, 'GETDATE()', '[dbo].[getlocaldate]()') 
    --PRINT @SQL
    EXEC (@SQL)
    FETCH NEXT FROM C INTO @SQL, @ojtype
END
CLOSE C
DEALLOCATE C

tentu saja memperluasnya untuk menangani fungsi, pemicu, dan sebagainya juga.

Ada beberapa peringatan:

  • Anda mungkin perlu sedikit lebih terang dan berurusan dengan ruang putih berbeda / ekstra antara CREATEdan PROCEDURE/ VIEW/<other> . Daripada REPLACEuntuk itu Anda mungkin lebih suka meninggalkan CREATEdi tempat dan mengeksekusi yang DROPpertama, tetapi ini berisiko meninggalkan sys.dependsdan teman-teman dari kilter mana ALTERmungkin tidak, juga jika ALTERgagal Anda setidaknya memiliki objek yang ada masih di tempat di mana dengan DROP+ CREATEAnda dapat tidak.

  • Jika kode Anda memiliki bau "pintar" seperti memodifikasi skema sendiri dengan TSQL ad-hoc maka Anda harus memastikan bahwa pencarian dan penggantian untuk CREATE-> ALTERtidak mengganggu itu.

  • Anda akan ingin menguji kembali seluruh aplikasi setelah operasi, apakah Anda menggunakan kursor atau metode ekspor + edit + jalankan.

Saya telah menggunakan metode ini untuk membuat pembaruan skema serupa di masa lalu. Ini adalah sedikit hack, dan terasa sangat jelek, tetapi kadang-kadang itu adalah cara termudah / tercepat.

Default dan kendala lainnya dapat dimodifikasi dengan cara yang sama, meskipun itu hanya dapat dijatuhkan dan dibuat ulang alih-alih diubah. Sesuatu seperti:

DECLARE C CURSOR FOR
        SELECT AlterDefaultSQL = 'ALTER TABLE [' +st.name+ '] DROP CONSTRAINT [' + si.name + '];'
                               + CHAR(10)
                               + 'ALTER TABLE [' +st.name+ '] ADD CONSTRAINT [' + si.name + '] DEFAULT '+REPLACE(si.definition, 'GETDATE()', '[dbo].[getlocaldate]()')+' FOR '+sc.name+';'
        FROM   sys.tables st
        JOIN   sys.default_constraints si ON si.parent_object_id = st.object_id
        JOIN   sys.columns sc ON sc.default_object_id = si.object_id
DECLARE @SQL NVARCHAR(MAX)
OPEN C
FETCH NEXT FROM C INTO @SQL
WHILE @@FETCH_STATUS = 0 BEGIN
    --PRINT @SQL
    EXEC (@SQL)
    FETCH NEXT FROM C INTO @SQL
END
CLOSE C
DEALLOCATE C

Beberapa lebih menyenangkan yang mungkin perlu Anda pertahankan: jika Anda mempartisi berdasarkan waktu maka bagian-bagian itu mungkin perlu diubah juga. Sementara mempartisi waktu lebih rinci daripada hari jarang, Anda bisa memiliki masalah di mana DATETIMEs ditafsirkan oleh fungsi pemartisian sebagai hari sebelumnya atau berikutnya tergantung pada zona waktu, membuat partisi Anda tidak selaras dengan kueri yang biasa Anda lakukan.

David Spillett
sumber
ya, peringatan adalah apa yang membuat ini sulit. Juga, ini tidak memperhitungkan nilai default kolom akun. Terima kasih
Lamak
Nilai default kolom dan batasan lainnya juga dapat dipindai untuk dalam sysskema dan dimodifikasi secara program.
David Spillett
Mungkin mengganti dengan misalnya CREATE OR ALTER PROCEDUREmembantu sekitar beberapa masalah pembuatan kode; masih mungkin ada masalah karena definisi yang disimpan akan membaca CREATE PROCEDURE(tiga! spasi) dan ini tidak cocok dengan CREATE PROCEDUREatau CREATE OR ALTER PROCEDURE.... ._.
TheConstructor
@TheConstructor - itulah yang saya maksud dengan wrt "extra whitespace". Anda bisa menyiasatinya dengan menulis fungsi yang memindai yang pertama CREATEyang tidak ada di dalam komentar dan menggantikannya. Saya tidak pernah melakukan ini / mirip di masa lalu tetapi tidak memiliki kode fungsi sekarang untuk memposting. Atau jika Anda dapat menjamin tidak ada definisi objek Anda memiliki komentar sebelumnya CREATE, abaikan masalah komentar dan temukan dan ganti instance pertama CREATE.
David Spillett
Saya telah mencoba pendekatan ini sendiri beberapa kali di masa lalu dan di-keseimbangan pendekatan Generate-Scripts lebih baik dan hampir selalu apa yang saya gunakan hari ini kecuali jumlah objek yang akan diubah ternyata relatif kecil.
RBarryYoung
5

Saya benar-benar menyukai jawaban David dan menyatakannya sebagai cara terprogram untuk melakukan sesuatu.

Tetapi Anda dapat mencoba ini hari ini untuk uji coba di Azure melalui SSMS:

Klik kanan database Anda -> Tugas -> Hasilkan Script ..

[Back Story] kami memiliki DBA junior yang memutakhirkan semua lingkungan pengujian kami ke SQL 2008 R2 sementara lingkungan produksi kami berada di SQL 2008. Ini adalah perubahan yang membuat saya merasa ngeri hingga hari ini. Untuk bermigrasi ke produksi, dari pengujian, kami harus membuat skrip dalam SQL, menggunakan skrip menghasilkan, dan dalam opsi lanjutan kami menggunakan opsi 'Jenis Data ke skrip: Skema dan Data' untuk menghasilkan file teks besar. Kami berhasil memindahkan pangkalan data uji R2 kami ke server SQL 2008 lawas kami - tempat pemulihan basis data ke versi yang lebih rendah tidak akan berfungsi. Kami menggunakan sqlcmd untuk memasukkan file besar - karena file sering kali terlalu besar untuk buffer teks SSMS.

Apa yang saya katakan di sini adalah bahwa opsi ini mungkin akan bekerja untuk Anda juga. Anda hanya perlu melakukan satu langkah tambahan dan mencari serta mengganti getdate () dengan [dbo] .getlocaldate dalam file teks yang dihasilkan. (Saya akan menempatkan fungsi Anda ke dalam database sebelum migrasi sekalipun).

(Saya tidak pernah ingin menjadi mahir dalam bantuan band pemulihan database ini, tetapi untuk sementara itu menjadi cara defacto dalam melakukan sesuatu. Dan, itu bekerja setiap saat.)

Jika Anda memilih rute ini, pastikan dan pilih tombol Advanced dan pilih semua opsi yang Anda butuhkan (baca masing-masing) untuk berpindah dari database lama ke database baru - seperti default yang Anda sebutkan. Tetapi berikan beberapa tes berjalan di Azure. Saya yakin Anda akan menemukan bahwa ini adalah salah satu solusi yang berfungsi - dengan sedikit upaya.

masukkan deskripsi gambar di sini

Menyengat
sumber
1

Ubah semua proc dan udf secara dinamis untuk mengubah nilai

    DECLARE @Text   NVARCHAR(max), 
        @spname NVARCHAR(max), 
        @Type   CHAR(5), 
        @Sql    NVARCHAR(max) 
DECLARE @getobject CURSOR 

SET @getobject = CURSOR 
FOR SELECT sc.text, 
           so.NAME, 
           so.type 
    FROM   sys.syscomments sc 
           INNER JOIN sysobjects so 
                   ON sc.id = so.id 
    WHERE  sc.[text] LIKE '%getdate()%' 

--and type in('P','FN') 
OPEN @getobject 

FETCH next FROM @getobject INTO @Text, @spname, @Type 

WHILE @@FETCH_STATUS = 0 
  BEGIN 
      IF ( @Type = 'P' 
            OR @Type = 'FN' ) 
        SET @Text = Replace(@Text, 'getdate', 'dbo.getlocaldate') 

      SET @Text = Replace(@Text, 'create', 'alter') 

      EXECUTE Sp_executesql 
        @Text 

      PRINT @Text 

      --,@spname,@Type 
      FETCH next FROM @getobject INTO @Text, @spname, @Type 
  END 

CLOSE @getobject 

DEALLOCATE @getobject  

 

    CREATE PROCEDURE [dbo].[Testproc1] 
AS 
    SET nocount ON; 

  BEGIN 
      DECLARE @CurDate DATETIME = Getdate() 
  END

Pemberitahuan dikomentari sysobjects Ketik kondisi kolom. Skrip saya hanya akan mengubah proc dan UDF.

Script ini akan mengubah semua Default Constraintyang mengandungGetDate()

    DECLARE @TableName      VARCHAR(300), 
        @constraintName VARCHAR(300), 
        @colName        VARCHAR(300), 
        @Sql            NVARCHAR(max) 
DECLARE @getobject CURSOR 

SET @getobject = CURSOR 
FOR SELECT ds.NAME, 
           sc.NAME AS colName, 
           so.NAME AS Tablename 
    --,ds.definition 
    FROM   sys.default_constraints ds 
           INNER JOIN sys.columns sc 
                   ON ds.object_id = sc.default_object_id 
           INNER JOIN sys.objects so 
                   ON so.object_id = ds.parent_object_id 
    WHERE  definition LIKE '%getdate()%' 

OPEN @getobject 

FETCH next FROM @getobject INTO @constraintName, @colName, @TableName 

WHILE @@FETCH_STATUS = 0 
  BEGIN 
      SET @Sql = 'ALTER TABLE ' + @TableName 
                 + ' DROP CONSTRAINT ' + @constraintName + '; ' 
                 + Char(13) + Char(10) + '           ' + Char(13) + Char(10) + '' 
      SET @Sql = @Sql + ' ALTER TABLE ' + @TableName 
                 + ' ADD CONSTRAINT ' + @constraintName 
                 + '          DEFAULT dbo.GetLocaledate() FOR ' 
                 + @colName + ';' + Char(13) + Char(10) + '          ' + Char(13) 
                 + Char(10) + '' 

      PRINT @Sql 

      EXECUTE sys.Sp_executesql 
        @Sql 

      --,@spname,@Type 
      FETCH next FROM @getobject INTO @constraintName, @colName, @TableName 
  END 

CLOSE @getobject 

DEALLOCATE @getobject   
KumarHarsh
sumber
1

Saya telah memutakhirkan jawaban Evan Carrolls, karena saya pikir ini adalah solusi terbaik . Saya belum dapat meyakinkan kolega saya bahwa mereka harus mengubah banyak kode C #, jadi saya harus menggunakan kode yang ditulis David Spillett. Saya telah memperbaiki beberapa masalah dengan UDF, Dynamic SQL, dan Schemas (tidak semua kode menggunakan "dbo.") Seperti ini:

DECLARE C CURSOR LOCAL STATIC FOR
        SELECT sm.definition, so.type
        FROM   sys.objects so
        JOIN   sys.all_sql_modules sm ON sm.object_id = so.object_id
        WHERE  so.type IN ('P', 'V')
        AND CHARINDEX('getdate()', sm.definition) > 0
        ORDER BY so.name

DECLARE @SQL NVARCHAR(MAX), @objtype NVARCHAR(MAX)
OPEN C
WHILE 1=1 BEGIN
    FETCH NEXT FROM C INTO @SQL, @objtype
    IF @@FETCH_STATUS <> 0 BREAK

    IF @objtype = 'P' SET @SQL = REPLACE(@SQL, 'CREATE PROCEDURE', 'ALTER PROCEDURE') 
    IF @objtype = 'P' SET @SQL = REPLACE(@SQL, 'CREATE   PROCEDURE', 'ALTER PROCEDURE') /* when you write "create or alter proc" */
    IF @objtype = 'V' SET @SQL = REPLACE(@SQL, 'CREATE VIEW'     , 'ALTER VIEW'     ) 
    IF CHARINDEX('getdate())''', @sql) > 0 BEGIN  /* when dynamic SQL is used */
        IF CHARINDEX('utl.getdate())''', @sql) = 0 SET @SQL = REPLACE(@SQL, 'GETDATE()', 'utl.getdate()') 
    end
    ELSE begin
        SET @SQL = REPLACE(@SQL, 'GETDATE()', 'CONVERT(DATETIME, CONVERT(datetimeoffset,  SYSDATETIME()) AT TIME ZONE ''Central Europe Standard Time'')') 
    end
    EXEC dbo.LongPrint @String = @sql    
    EXEC (@SQL)
END
CLOSE C
DEALLOCATE C

dan batasan default seperti ini:

DECLARE C CURSOR LOCAL STATIC FOR
        SELECT AlterDefaultSQL = 'ALTER TABLE [' +sch.name+ '].[' +st.name+ '] DROP CONSTRAINT [' + si.name + '];'
                               + CHAR(10)
                               + 'ALTER TABLE [' +sch.name+ '].[' +st.name+ '] ADD CONSTRAINT [' + si.name + '] DEFAULT '+REPLACE(si.definition, 'GETDATE()', 'CONVERT(DATETIME, CONVERT(datetimeoffset,  SYSDATETIME()) AT TIME ZONE ''Central Europe Standard Time'')')+' FOR '+sc.name+';'
        FROM   sys.tables st
        JOIN   sys.default_constraints si ON si.parent_object_id = st.object_id
        JOIN   sys.columns sc ON sc.default_object_id = si.object_id
        INNER JOIN sys.schemas sch ON sch.schema_id = st.schema_id
        WHERE CHARINDEX('getdate()', si.definition) > 0
        ORDER BY st.name, sc.name

DECLARE @SQL NVARCHAR(MAX)
OPEN C
WHILE 1=1 BEGIN
    FETCH NEXT FROM C INTO @SQL
    IF @@FETCH_STATUS <> 0 BREAK

    EXEC dbo.LongPrint @String = @sql  
    EXEC (@SQL)
    FETCH NEXT FROM C INTO @SQL
END
CLOSE C
DEALLOCATE C


UDF
Saran untuk menggunakan UDF yang mengembalikan tanggal dan waktu todays terlihat bagus, tapi saya pikir masih ada masalah kinerja yang cukup dengan UDF, jadi saya memilih untuk menggunakan solusi ZONA WAKTU yang sangat panjang dan jelek.

Henrik Staun Poulsen
sumber