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


27

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

特に、Azure SQLデータベースはUTC時間(タイムゾーンではない)でのみ機能し、現地時間を必要とするため、データベース内のGETDATE() すべての場所の使用を変更する必要があります。

私のタイムゾーンで正しく機能するローカル時間を取得するために、ユーザー定義関数を作成しました。

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への移行は必須です。このデータベースは、常にこのタイムゾーンで使用されます(使用されます)。

回答:


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

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

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

  4. データの移行を実行します。

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


実際には、このアプローチは見た目よりも複雑ですが、おそらく正しい正解です。私はこれほど多くのスコアに似たタスクを実行する必要があり、利用可能なすべてのアプローチを試しましたが、より良い(または実際に近い)ものも見つかりませんでした。他のアプローチ最初素晴らしいように見えますが、すぐに見落としや落とし穴の悪夢のような泥沼になります。
–RBarryYoung

15

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

私は別の方法で対処します。データベース内のすべてのタイムスタンプをUTCに変換し、UTCを使用してフローに進みます。別のtzのタイムスタンプが必要な場合は、AT TIME ZONE(上記のように)指定されたTZ(アプリ用)のタイムスタンプをレンダリングする(上記と同様)を使用して生成列を作成できます。しかし、UTCをアプリに返すだけで、アプリでそのロジック(表示ロジック)を記述することを真剣に検討します。


それが単なるデータベースのものである場合、私はこれを検討するかもしれませんが、その変更は深刻なリファクタリングを必要とする他の多くのアプリやソフトウェアに影響を与えます。だから、悲しいことに、それは私にとって選択ではありません
-Lamak

5
「アプリとソフトウェア」のいずれもgetdate()を使用しないという保証はありますか?すなわち、アプリに埋め込まれたSQLコード。保証できない場合は、データベースをリファクタリングして別の関数を使用すると、一貫性が失われます。
ミスターマグー

@MisterMagooそれは店での慣習に依存しますが、率直に言ってこれは非常に小さな関心事だと思います、そして問題を回避するために質問をしてから実際にiを修正するのに時間がかかるとは思いません。この質問 Azureでない場合、面白いと思います。なぜなら、私はそれをハックして、より多くのフィードバックを提供できるからです。ただし、クラウドの問題はひどいです。彼らはそれをサポートしていないので、あなたはあなたの側で何かを変えなければなりません。私は答えに示されたルートに行き、それを正しく行うことに時間を費やすことを望みます。また、いつものように、Azureに移行したときに何かが機能するという保証はありません。
エヴァンキャロル

@EvanCarroll、すみません、コメントを読み直しただけで、意図をうまく表現できませんでした!私はあなたの答えを支持し(賛成)、データベースでgetdate()の使用をgetlocaldate()に変更するだけの提案は、アプリ側からの矛盾に開放されたままになり、さらにはより大きな問題に石膏を貼る。100%があなたの答えに同意します。コアの問題を修正するのが正しいアプローチです。
ミスターマグー

@MisterMagoo私はあなたの懸念を理解するが、この場合には、私が唯一のストアドプロシージャを介してデータベースとそのアプリケーションとソフトウェアの相互作用を保証することができます
Lamak

6

エクスポート、手動で編集、再実行する代わりに、次のような方法でデータベースで直接ジョブを実行できます。

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に応じて前日または翌日であると解釈され、パーティションが通常のクエリと一致しないままになる問題が発生する可能性があります。


ええ、警告はこれを難しくするものです。また、これは列のデフォルト値を考慮しません。とにかく感謝
-Lamak

列のデフォルト値およびその他の制約もsysスキーマでスキャンし、プログラムで変更できます。
デビッドスピレット

例えばCREATE OR ALTER PROCEDURE、コード生成の問題を回避するのに役立つかもしれません。格納された定義が読み取るためCREATE PROCEDURE(3!スペース)まだ問題がある可能性があり、これは... ._にも一致しCREATE PROCEDUREませCREATE OR ALTER PROCEDUREん。
TheConstructor

@TheConstructor-それは私が「余分な空白」について言及していたことです。これを回避するにはCREATE、コメント内にない最初のものをスキャンし、それを置き換える関数を作成します。私は過去にこれをしませんでしたが、今すぐ投稿するのに便利な関数のコードを持っていません。または、オブジェクト定義の前CREATEにコメントがないことを保証できる場合は、コメントの問題を無視して、の最初のインスタンスを見つけて置き換えてくださいCREATE
デビッドスピレット

私はこのアプローチを過去に何度も試しましたが、Generate-Scriptsアプローチはバランスがとれており、変更するオブジェクトの数が比較的少ないことが判明しない限り、ほぼ常に今日使用しています。
–RBarryYoung

5

私はデビッドの答えが本当に好きで、それをプログラム的な方法で行うことに賛成しました。

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

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

[バックストーリー]実稼働環境がSQL 2008であった間に、すべてのテスト環境をSQL 2008 R2にアップグレードした後輩DBAがいました。これは私が今日にうんざりする変更です。テストから実稼働環境に移行するには、生成スクリプトを使用してSQL内でスクリプトを生成する必要があり、高度なオプションでは「スクリプトするデータのタイプ:スキーマとデータ」オプションを使用して大規模なテキストファイルを生成しました。テストR2データベースをレガシーSQL 2008サーバーに正常に移動することができました。以前のバージョンのデータベースへの復元は機能しませんでした。sqlcmdを使用して大きなファイルを入力しました。ファイルはしばしばSSMSテキストバッファーには大きすぎるためです。

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

(私はこのデータベース復元のバンドエイドに精通したくはありませんでしたが、しばらくの間それは物事を行うための事実上の方法になりました。そして、それは毎回働きました。)

このルートを選択する場合は、必ず[詳細設定]ボタンを選択し、古いデータベースから新しいデータベースに移動するために必要なすべてのオプションを選択してください(各オプションをお読みください)。ただし、Azureでいくつかのテストを実行してください。これは、わずかな労力で機能するソリューションの1つであることがわかります。

ここに画像の説明を入力してください


1

すべての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列の条件がコメント化されていることに注意してください。私のスクリプトはprocとUDFのみを変更します。

このスクリプトは、以下Default Constraintを含むすべてを変更しますGetDate()

    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

これが最善の解決策だと思うので、エヴァンキャロルの答えを支持しました。私は同僚に多くの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


UDF
今日の日付と時刻を返すUDFを使用する提案は素晴らしいようですが、UDFにはまだ十分なパフォーマンスの問題があると思うので、非常に長くていAT TIME ZONEソリューションを使用することにしました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.