web-dev-qa-db-ja.com

データベース全体でのGETDATE()の使用の変更

オンプレミスのSQL Server 2017データベースをAzure SQLデータベースに移行する必要があり、通過する制限がかなりあるため、いくつかの課題に直面しています。

特に、Azure SQLデータベースはUTC時間(タイムゾーンなし)でのみ機能し、ローカル時間を必要とするため、GETDATE()everywhereデータベース内。これは、予想以上の作業であることが証明されています。

私は自分のタイムゾーンで正しく動作する現地時間を取得するユーザー定義関数を作成しました。

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

私が問題を抱えている問題は、すべてのビュー、ストアドプロシージャ、計算列、デフォルト値、その他の制約などで、この関数を使用して実際にGETDATE()を変更することです。

この変更を実装する最良の方法は何ですか?

マネージドインスタンス のパブリックプレビューを表示しています。 GETDATE()でも同じ問題が発生するため、この問題の解決にはなりません。 Azureへの移行は必須です。このデータベースは、常にこのタイムゾーンで使用されます(使用されます)。

27
Lamak
  1. SQL Serverツールを使用して、データベースオブジェクトの定義を、テーブル、ビュー、トリガー、SP、関数などを含むSQLファイルにエクスポートします。

  2. テキスト"GETDATE()"を見つけて"[dbo].[getlocaldate]()"に置き換えることができるテキストエディターを使用して、SQLファイルを編集します(最初にバックアップを作成します)。

  3. Azure SQLで編集済みのSQLファイルを実行して、データベースオブジェクトを作成します...

  4. データのマイグレーションを実行します。

ここに、Azureのドキュメントからの参照があります。 SQL Azureのスクリプトの生成

17
AMG

この変更を実装する最良の方法は何ですか?

私は逆に働きます。データベース内のすべてのタイムスタンプをUTCに変換し、UTCを使用してフローを実行します。別のtzのタイムスタンプが必要な場合は、(上記のように)AT TIME ZONEを使用して生成された列を作成し、指定されたTZ(アプリ用)にタイムスタンプをレンダリングできます。しかし、私はUTCをアプリに戻し、そのロジック(表示ロジック)をアプリに書き込むことを真剣に検討します。

15
Evan Carroll

エクスポートして手動で編集して再実行するのではなく、次のような方法でデータベース内で直接ジョブを実行することができます。

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

もちろん、関数やトリガーなどを処理するように拡張することもできます。

注意点がいくつかあります。

  • もう少し明るくして、CREATEPROCEDURE/VIEW/<other>の間の異なる/余分な空白を処理する必要がある場合があります。そのため、REPLACEではなくCREATEをそのままにしてDROPを最初に実行することをお勧めしますが、これによりsys.dependsや友人がALTERが失敗する可能性があるキルター、ALTERが失敗した場合でも、少なくともDROP + CREATEが存在しない場所に既存のオブジェクトがまだ残っています。

  • 独自のスキーマをアドホックTSQLで変更するような「賢い」匂いがコードにある場合は、CREATE-> ALTERの検索と置換が干渉しないことを確認する必要があります。それと。

  • カーソルを使用するか、export + edit + runメソッドを使用するかに関係なく、操作後にアプリケーション全体を回帰テストする必要があります。

この方法を使用して、過去に同様のスキーマ全体の更新を行いました。これは少しハックのようで、見苦しい感じがしますが、最も簡単で迅速な方法の場合もあります。

デフォルトおよび他の制約も同様に変更できますが、変更および変更はできず、ドロップおよび再作成のみが可能です。何かのようなもの:

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

あなたが対処する必要があるかもしれないいくつかのより楽しい:時間によって分割しているなら、それらの部分も変更する必要があるかもしれません。時間によるパーティション分割が日ごとに行われることはまれですが、パーティション分割関数によってDATETIMEsがtimezineに応じて前日または翌日として解釈され、パーティションが通常のクエリと整列されないままになるという問題が発生する可能性があります。

6
David Spillett

私はデイビッドの答えが本当に好きで、プログラムによる方法でそれを支持しました。

しかし、SSMSを介してAzureでテスト実行するために、今日これを試すことができます。

データベースを右クリック->タスク->スクリプトの生成..

[バックストーリー]実稼働環境がSQL 2008にある間に、すべてのテスト環境をSQL 2008 R2にアップグレードするジュニアDBAがいました。テストから本番環境に移行するには、生成スクリプトを使用してSQL内でスクリプトを生成する必要があり、詳細オプションでは、[スクリプトするデータのタイプ:スキーマとデータ]オプションを使用して大規模なテキストファイルを生成しました。テストR2データベースを従来のSQL 2008サーバーに正常に移動できました。以前のバージョンへのデータベースの復元は機能しませんでした。大きなファイルを入力するためにsqlcmdを使用しました。ファイルはSSMSテキストバッファーには大きすぎることが多いためです。

ここで私が言っていることは、このオプションはおそらくあなたにも役立つだろうということです。追加の手順を1つ実行し、生成されたテキストファイルでgetdate()を検索して[dbo] .getlocaldateに置き換えるだけです。 (ただし、移行前に関数をデータベースに入れます)

(私はデータベースの復元のこのバンドエイドに熟練したくなかったが、しばらくの間それは物事を実行するための事実上の方法になりました。そして、それは毎回うまくいきました。)

このルートを選択する場合は、必ず[詳細設定]ボタンを選択し、必要なすべてのオプションを選択して(それぞれを読んで)、古いデータベースから新しいデータベース-あなたが言及したデフォルトのように。しかし、Azureでいくつかのテストを実行します。たぶん、これは有効な解決策の1つであることに気付くでしょう-少しの努力で。

enter image description here

5
Sting

私はこれがbestソリューションであると思うので、Evan Carrollsの回答に賛成しています。多くのC#コードを変更する必要があることを同僚に納得させることができなかったため、David Spillettが作成したコードを使用する必要がありました。以下のように、UDF、動的SQL、およびスキーマ(すべてのコードが「dbo。」を使用するわけではない)に関するいくつかの問題を修正しました。

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

そして、このようなデフォルトの制約:

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


DF
今日の日付と時刻を返すUDFを使用するという提案は良さそうですが、UDFにはまだ十分なパフォーマンスの問題があると思うので、非常に長くて醜いATタイムゾーンソリューション。

すべてのprocおよびudfを動的に変更して値を変更する

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

コメントされた通知 sysobjects Type column condition.MyスクリプトはprocとUDFのみを変更します。

このスクリプトは、GetDate()を含むすべての_Default Constraint_を変更します

_    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
KumarHarsh