在整个数据库中更改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是必需的。始终在该时区使用(并将使用)此数据库。

Answers:


17
  1. 使用SQL Server工具将数据库对象定义导出到SQL文件,该文件应包括:表,视图,触发器,SP,函数等

  2. 使用任何文本编辑器来编辑SQL文件(首先进行备份),该文本编辑器允许您查找文本"GETDATE()"并将其替换为"[dbo].[getlocaldate]()"

  3. 在Azure SQL中运行编辑的SQL文件以创建数据库对象...

  4. 执行数据迁移。

在这里,您可以从azure文档中获得参考:为SQL Azure生成脚本


尽管实际上这种方法比听起来复杂得多,但它可能是正确和最佳的答案。我不得不做很多次类似的任务,并且尝试了所有可用的方法,但没有发现任何更好的方法(或者甚至是关闭方法)。最初,其他方法似乎很棒,但是它们很快就变成了监督和陷阱的噩梦般的泥潭。
RBarryYoung

15

实施此更改的最佳方法是什么?

我会反过来工作。将数据库中的所有时间戳转换为UTC,然后使用UTC并顺其自然。如果需要使用其他tz的时间戳,则可以使用AT TIME ZONE(如上所述)创建一个生成的列,该列在指定的TZ(对于应用程序)中呈现时间戳。但是,我会认真考虑将UTC返回到应用程序,然后在应用程序中编写该逻辑-显示逻辑。


如果只是数据库的事情,我可能会考虑这一点,但是这种变化会影响很多其他需要认真重构的应用程序和软件。因此,可悲的是,这不是我的选择
Lamak

5
您可以保证没有任何“应用程序和软件”使用getdate()吗?即在应用程序中嵌入的sql代码。如果不能保证,将数据库重构为使用其他函数将导致不一致。
马古先生

@MisterMagoo这取决于商店的做法,坦率地说,我认为这是一个非常小的问题,我看不出要花多少时间来解决问题然后实际解决问题。如果这个问题不是 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

您可能还需要面对一些更多的乐趣:如果按时间分区,那么这些部分也可能需要更改。尽管按时间按天划分的时间分区比较少,但您可能会遇到问题,DATETIME分区函数将s解释为前一天或后一天,具体取决于timezine,从而使分区与常规查询不一致。


是的,需要注意的是使这变得困难的原因。另外,这没有考虑列的默认值。无论如何
Lamak '18

列默认值和其他约束也可以在sys架构中进行扫描并以编程方式进行修改。
David Spillett

也许替换为例如CREATE OR ALTER PROCEDURE一些代码生成问题的帮助;仍然存在问题,因为存储的定义将读取CREATE PROCEDURE(三个空格),并且既不匹配CREATE PROCEDURE也不是CREATE OR ALTER PROCEDURE.._。
TheConstructor

@TheConstructor-那就是我指的是wrt“额外的空格”。您可以通过编写一个函数来解决此问题,该函数扫描CREATE不在注释内的第一个函数并将其替换。我过去没有这个/相似之处,但是现在没有方便发布的函数代码。或者,如果您可以保证没有任何对象定义前面有注释CREATE,请忽略注释问题,而只是查找并替换的第一个实例CREATE
David Spillett

过去,我自己多次尝试过这种方法,总的来说,Generate-Scripts方法更好,并且几乎一直是我今天使用的方法,除非要更改的对象数量相对较少。
RBarryYoung

5

我真的很喜欢David的回答,并赞成以编程方式进行处理。

但是您今天可以尝试通过SSMS在Azure中进行测试运行:

右键单击数据库->任务->生成脚本。

[背景资料]我们有一个初级DBA,他在生产环境为SQL 2008时将所有测试环境都升级到SQL 2008 R2。这是我的今天。为了从测试迁移到生产环境,我们必须使用生成脚本在SQL中生成脚本,并且在高级选项中,我们使用了“要编写脚本的数据类型:模式和数据”选项来生成大型文本文件。我们能够成功地将测试R2数据库移动到旧版SQL 2008服务器上,在该服务器上无法将数据库还原到较低版本。我们使用sqlcmd输入大文件-因为文件对于SSMS文本缓冲区通常太大。

我在这里说的是,此选项也可能对您也有效。您只需要执行另一步骤,并在生成的文本文件中搜索并用[dbo] .getlocaldate替换getdate()。(虽然我会在迁移之前将您的函数放入数据库中)。

(我从不希望精通这种数据库还原的创可贴,但是一段时间以来,它已成为一种事实上的处理方式。而且,它每次都能奏效。)

如果选择此路线,请确保并选择“高级”按钮,然后选择从旧数据库移动到新数据库所需的所有选项(阅读每个选项),就像您提到的默认值一样。但是在Azure中进行一些测试运行。我敢打赌,您会发现这是一个行之有效的解决方案-只需一点点努力。

在此处输入图片说明


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类型列条件已注释。我的脚本将仅更改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

我支持Evan Carrolls的答案,因为我认为这是最好的解决方案。我无法说服同事,他们应该更改很多C#代码,因此我不得不使用David Spillett编写的代码。我已经解决了UDF,Dynamic SQL和Schemas的一些问题(并非所有代码都使用“ 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解决方案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.