Tüm veritabanında GETDATE () kullanımının değiştirilmesi


27

Bir şirket içi SQL Server 2017 veritabanını bir Azure SQL veritabanına geçirmem gerekiyor ve bu konuda bir takım zorluklarla karşı karşıya kalıyorum.

Özellikle, bir Azure SQL veritabanı yalnızca UTC saatinde çalıştığından (saat dilimi yok) ve yerel saate ihtiyacımız olduğundan, beklediğimden daha fazla iş olduğunu kanıtlamış olan veritabanındaki GETDATE() her yerde kullanımını değiştirmek zorundayız .

Saat dilimim için doğru çalışan yerel saati bulmak için kullanıcı tanımlı bir işlev oluşturdum:

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

Sorun yaşıyorum, aslında GETDATE()bu işlevle her görünümde, saklı yordamda, hesaplanan sütunlarda, varsayılan değerlerde, diğer kısıtlamalarda vs. değişiklik yapmak.

Bu değişikliği uygulamanın en iyi yolu ne olabilir?

Yönetilen Örneklerin genel önizlemesindeyiz . Hala aynı sorunu var GETDATE(), bu yüzden bu soruna yardımcı olmuyor. Azure'a geçmek bir gerekliliktir. Bu veritabanı her zaman bu zaman diliminde kullanılır (ve kullanılır).

Yanıtlar:


17
  1. Veritabanı nesneleri tanımını, içermesi gereken bir SQL dosyasına vermek için SQL Server aracını kullanın: tablolar, görünümler, tetikleyiciler, SP'ler, işlevler vb.

  2. Metni bulup "GETDATE()"değiştirmenize olanak tanıyan herhangi bir metin düzenleyiciyi kullanarak SQL dosyasını düzenleyin (önce yedekleme yapın)."[dbo].[getlocaldate]()"

  3. Veritabanı nesnelerinizi oluşturmak için düzenlenen SQL dosyasını Azure SQL'de çalıştırın ...

  4. Veri geçişini yürütün.

Burada azure belgelerinden bir referansa sahipsiniz: SQL Azure için Komut Dosyaları Oluşturma


Uygulamada bu yaklaşım göründüğünden daha karmaşık olsa da, muhtemelen doğru ve en iyi cevaptır. Bu kadar çok zaman dilimine benzer işler yapmak zorunda kaldım ve mevcut olan her yaklaşımı denedim ve daha iyi bir şey bulamadım (hatta yakın, hatta). diğer yaklaşımlar ilk bakışta harika gözüküyor , ancak hızlı bir şekilde kabus gözetim ve yakalama kabusu haline geliyorlar.
RBarryYoung

15

Bu değişikliği uygulamanın en iyi yolu ne olabilir?

Diğer yoldan çalışırdım. Veritabanındaki tüm zaman damgalarınızı UTC'ye dönüştürün ve sadece UTC kullanın ve akışa devam edin. Farklı bir tz'de zaman damgasına ihtiyacınız varsa, AT TIME ZONE(yukarıda yaptığınız gibi) belirtilen TZ'de (uygulama için) zaman damgasını oluşturan bir sütun oluşturabilirsiniz . Ancak, sadece UTC'nin uygulamaya geri dönmesini ve uygulamadaki bu mantığı - ekran mantığını - yazmayı ciddi şekilde düşünürdüm.


eğer sadece bir veritabanı olayıysa, bunu düşünebilirim, fakat bu değişiklik ciddi refactoring gerektiren birçok uygulama ve yazılımı etkiliyor. Yani, ne yazık ki, bu benim için bir seçim değil
Lamak

5
"Uygulamalar ve yazılım" ın hiçbirinin getdate () kullanmayacağının garantisi nedir? yani uygulamalar içine gömülü sql kodu. Bunu garanti edemezseniz, veritabanını farklı bir işlevi kullanmak için yeniden düzenlemek sadece tutarsızlığa yol açacaktır.
Mister Magoo

@MisterMagoo Bu dükkandaki uygulamalara bağlı, açıkçası bunun çok küçük bir endişe olduğunu düşünüyorum ve soruyu sorunun etrafında çalışmasını istemek için daha fazla zaman harcayarak göremiyorum. Bu soru eğer ilginç olurdu değildi Azure bunu kesmek ve daha fazla geri bildirimde çünkü. Cloud olayı berbat olsa da: desteklemiyorlar, bu yüzden kendi tarafınızdaki bir şeyi değiştirmeniz gerekiyor. Cevabımda verilen rotaya gitmeyi ve zamanını doğru yapmak için harcamayı tercih ederim. Ayrıca, Azure'a taşındığınızda her zaman olduğu gibi bir şeyin işe yarayacağının garantisi de yoktur.
Evan Carroll,

@EvanCarroll, üzgünüm yorumumu yeniden okudum ve niyetimi iyi ifade etmedim! Cevabınıza destek olmak (artırılmış) ve veri tabanındaki getdate () kullanımının, veri tabanındaki getlocaldate () yöntemine değiştirilmesinin önerilerini uygulama tarafındaki tutarsızlıklara açık bırakacağını ve bunun da yalnızca daha büyük bir problem üzerine alçı yapıştırma. Yanıtınıza% 100 katılıyorum, çekirdek sorunu düzeltmek doğru bir yaklaşım.
Bay Magoo,

@MisterMagoo Endişenizi anlıyorum, ancak bu durumda, uygulamaların ve yazılımların veritabanıyla yalnızca saklı yordamlarla etkileşimde bulunmalarını garanti edebilirim
Lamak

6

Dışa aktarma, el ile düzenleme ve yeniden çalıştırma yerine, işi doğrudan veritabanında yapmayı deneyebilirsiniz:

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

Tabii ki, işlevler, tetikleyiciler vb. ile başa çıkmak için genişletiliyor.

Birkaç uyarı var:

  • Biraz daha parlak olmanız CREATEve PROCEDURE/ VIEW/ arasında farklı / ekstra beyaz boşluklarla uğraşmanız gerekebilir <other>. Yerine REPLACEyerine bırakmayı tercih olabileceği için CREATEbir yerde ve yürütmek DROPilk, ancak bu riskler bırakarak sys.dependsdışında tutulmuş ve arkadaşlar nerede ALTERolmayabilir, ayrıca eğer ALTERhala yerinde mevcut nesne var nerede ile en azından sizi başarısız DROP+ CREATEseni may değil.

  • Kodunuzda "akıllı" bir koku varsa, kendi TSEM'i geçici TSQL ile değiştirmek gibi kokuyorsa, aramanın ve CREATE-> değiştirmenin bununla çakışmadığından emin olmanız gerekir ALTER.

  • İşlemden sonra, imleci veya export + edit + run yöntemlerini kullanıp kullanmadığınızı görmek için tüm uygulamaları regresyon testi yapmak isteyeceksiniz.

Geçmişte benzer şema çapında güncellemeler yapmak için bu yöntemi kullandım. Biraz kesmek ve oldukça çirkin hissediyor, ama bazen en kolay / en hızlı yol.

Varsayılanlar ve diğer kısıtlamalar da aynı şekilde değiştirilebilir, ancak bunlar yalnızca değiştirilmek yerine düşürülebilir ve yeniden oluşturulabilir. Gibi bir şey:

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

Başa çıkmanız gerekebilecek daha eğlenceli: Zamanla bölümlere ayırıyorsanız bu parçaların da değişmesi gerekebilir. Zamana göre daha ayrıntılı bir şekilde bölümlere ayırmak nadiren gerçekleşirken nadiren zaman DATETIMEdilimlerine bağlı olarak bölümleme işlevinin önceki veya sonraki gün olarak yorumlandığı, bölümlerinizi olağan sorgularınızla aynı hizaya getirmediğiniz için sorunlarınız olabilir.


evet, uyarılar bunu zorlaştırır. Ayrıca, bu sütun varsayılan değerlerini dikkate almaz. Yine de teşekkürler
Lamak

Sütun varsayılan değerleri ve diğer kısıtlamalar da sysşemada taranabilir ve programlı olarak değiştirilebilir.
David Spillett

Belki de örneğin ile değiştirmek CREATE OR ALTER PROCEDUREbazı kod üretme sorunlarına yardımcı olabilir; depolanan tanımın okuyacağı sorunlar olabilir CREATE PROCEDURE(üç! boşluk) ve bu ne eşleşmiyor CREATE PROCEDUREne de CREATE OR ALTER PROCEDURE… ._.
TheCononstructor

@Constructor - "ekstra boşluk" ile wrt demiştim. Bunu, CREATEbir yorumun içinde olmayan ve yerini alan bir işlev yazarak çözebilirsiniz. Geçmişte bunu / benzerini yapmıyorum ancak şu anda yayınlamak için işlevin kodunu kullanamıyorum. Veya nesne tanımlarınızdan hiçbirinin önceki açıklamalara sahip olmadığını garanti edebiliyorsanız CREATE, yorumlar sorununu yoksayın ve ilk örneğini bulup değiştirin CREATE.
David Spillett

Bu yaklaşımı kendim geçmişte defalarca denedim ve Dengede-Script yaklaşımını daha iyi bir şekilde dengeledim ve değiştirilecek nesne sayısı nispeten küçük olmadığı sürece hemen hemen her zaman kullandığım şeydi.
RBarryYoung

5

David'in cevabını gerçekten seviyorum ve programlı bir şeyler yapmanın yolunu seçti.

Fakat bugün bunu SSMS üzerinden Azure'da yapılacak bir deneme için deneyebilirsiniz:

Veritabanına sağ tıklayın -> Görevler -> Komut Dosyaları Oluştur ..

[Geri Hikaye], üretim ortamlarımız SQL 2008'deyken tüm test ortamlarımızı SQL 2008 R2'ye yükselten bir DBA'mız vardı. Bu, beni bu güne sıkıştıran bir değişiklik. Prodüksiyondan üretime geçmek için, SQL'de komut dosyaları kullanarak komut dosyaları oluşturmak zorunda kaldık ve gelişmiş seçeneklerde, büyük bir metin dosyası oluşturmak için 'Kodda Veri Türü: Şema ve Veri' seçeneğini kullandık. Test R2 veritabanlarımızı başarılı bir şekilde eski SQL 2008 sunucularımıza taşıyabildik. Dosyaları büyük dosyalara girmek için sqlcmd kullandık - dosyalar genellikle SSMS metin arabelleği için çok büyüktü.

Burada söylediğim şey, bu seçeneğin muhtemelen sizin için de işe yarayacağı. Sadece bir adım daha yapmanız ve oluşturulan metin dosyasında getdate () 'i [dbo] .getlocaldate ile aramanız ve değiştirmeniz gerekecektir. (İşlevinizi, göç etmeden önce veritabanına koyardım).

(Bir veritabanı geri yüklemesinin bu yara bandı yardımında yetkin olmak istemedim, ancak bir süre bir şeyleri yapmanın yanlış bir yolu oldu. Her seferinde işe yaradı.)

Bu rotayı seçerseniz, Gelişmiş düğmesini seçtiğinizden emin olun ve eski veritabanından yeni veritabanına geçmek için ihtiyacınız olan tüm seçenekleri seçin (her birini okuyun) - belirttiğiniz varsayılanlar gibi. Ancak, Azure'da birkaç deneme çalışması yapın. Bahse girerim, bunun işe yarayan bir çözüm olduğunu göreceksiniz - bir hayli çaba ile.

görüntü tanımını buraya girin


1

Tüm proc ve udf değerlerini değiştirmek için dinamik olarak değiştirin

    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

Dikkat yorumlanan sysobjects Tür sütun koşulu. Komut dosyası yalnızca proc ve UDF'yi değiştirecek.

Bu betiğin Default Constraintiçerdiği her şeyi değiştirecekGetDate()

    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   

1

Bunun en iyi çözüm olduğunu düşündüğümden Evan Carroll'un cevabını değiştirdim . Meslektaşlarımı çok fazla C # kodu değiştirmeleri gerektiği konusunda ikna edemedim, bu yüzden David Spillett'in yazdığı kodu kullanmak zorunda kaldım. UDF'ler, Dinamik SQL ve Şemalar ile ilgili birkaç sorunu düzelttim (kodun tümü "dbo" kullanmıyor):

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

ve bunun gibi varsayılan kısıtlamalar:

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'ler
Bugünün tarihine ve saatine geri dönen bir UDF kullanma önerisi güzel görünüyor, ancak UDF'lerde hala yeterince performans sorunu olduğunu düşünüyorum, bu yüzden çok uzun ve çirkin AT TIME ZONE çözümünü kullanmayı seçtim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.