Изменение использования GETDATE () во всей базе данных

27

Мне нужно перенести локальную базу данных SQL Server 2017 в базу данных SQL Azure, и я сталкиваюсь с некоторыми проблемами, так как есть довольно много ограничений, которые необходимо пройти.

В частности, поскольку база данных SQL Azure работает только во времени 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 является обязательным требованием. Эта база данных используется (и будет использоваться) всегда в этом часовом поясе.

Lamak
источник

Ответы:

17
  1. Используйте инструмент SQL Server для экспорта определения объектов базы данных в файл SQL, который должен включать в себя: таблицы, представления, триггеры, SP, функции и т. Д.

  2. Отредактируйте файл SQL (сначала сделайте резервную копию), используя любой текстовый редактор, который позволяет найти текст "GETDATE()"и заменить его на"[dbo].[getlocaldate]()"

  3. Запустите отредактированный файл SQL в Azure SQL, чтобы создать объекты базы данных ...

  4. Выполните миграцию данных.

Вот ссылка на документацию по Azure : создание сценариев для SQL Azure

AMG
источник
Хотя на практике этот подход сложнее, чем кажется, это, вероятно, правильный и лучший ответ. Мне приходилось выполнять задачи, подобные этим, десятки раз, и я пробовал каждый доступный подход, и я не нашел ничего лучше (или даже близко, правда). другие подходы на первый взгляд кажутся великолепными, но они быстро превращаются в кошмарное болото упущений и ошибок.
RBarryYoung
15

Как лучше всего осуществить это изменение?

Я бы работал наоборот. Преобразуйте все свои временные метки в базе данных в UTC, и просто используйте UTC и продолжайте. Если вам нужна временная метка в другом tz, вы можете создать сгенерированный столбец, используя AT TIME ZONE(как вы делали выше) визуализацию временной метки в указанном TZ (для приложения). Но я бы серьезно подумал о том, чтобы просто вернуть UTC в приложение и написать эту логику - логику отображения - в приложении.

Эван Кэрролл
источник
если бы это была просто проблема с базой данных, я мог бы рассмотреть это, но это изменение затрагивает многие другие приложения и программное обеспечение, которые требуют серьезного рефакторинга. Так что, к сожалению, это не выбор для меня
Ламак
5
Какая у вас гарантия того, что ни одно из «приложений и программ» не использует getdate ()? т.е. SQL-код, встроенный в приложения. Если вы не можете этого гарантировать, рефакторинг базы данных для использования другой функции приведет к несогласованности.
Мистер Магу
@MisterMagoo Это зависит от практики в магазине, откровенно говоря, я думаю, что это очень незначительная проблема, и я не вижу, что у меня уходит так много времени, чтобы задать вопрос, чтобы обойти проблему, а затем фактически решить i. Было бы интересно, если бы этот вопрос не касался Azure, потому что я мог бы его взломать и дать вам больше отзывов. Облачные вещи - это отстой: они не поддерживают это, поэтому вы должны что-то изменить на своей стороне. Я бы предпочел пойти по маршруту, указанному в моем ответе, и потратить время на то, чтобы сделать это правильно. Кроме того, у вас нет никаких гарантий, что когда вы перейдете на Azure, все будет работать, как всегда.
Эван Кэрролл
@EvanCarroll, извините, я просто перечитал свой комментарий и не очень хорошо выразил свои намерения! Я хотел поддержать ваш ответ (upvoted) и поднять вопрос о том, что предложения просто изменить использование getdate () на getlocaldate () в базе данных оставят их открытыми для несоответствий со стороны приложения, и, кроме того, это всего лишь наклеить штукатурку на большую проблему. 100% согласны с вашим ответом, решение основной проблемы - правильный подход.
Мистер Магу
@MisterMagoo Я понимаю вашу озабоченность, но в этом случае я могу гарантировать, что приложения и программное обеспечение взаимодействуют с базой данных только с помощью хранимых процедур
Ламак
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

конечно, расширяя его, чтобы иметь дело с функциями, триггерами и так далее.

Есть несколько предостережений:

  • Возможно, вам нужно быть немного ярче и иметь дело с разным / дополнительным пробелом между CREATEи PROCEDURE/ 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функция интерпретации интерпретируется как предыдущий или следующий день в зависимости от времени, оставляя ваши разделы не выровненными с вашими обычными запросами.

Дэвид Спиллетт
источник
да, предостережения делают это трудным. Кроме того, это не учитывает значения столбца по умолчанию. Все равно спасибо
Ламак
Значения столбца по умолчанию и другие ограничения также можно отсканировать в sysсхеме и программно изменить.
Дэвид Спиллетт
Возможно, заменить на, например, CREATE OR ALTER PROCEDUREпомогает решить некоторые проблемы генерации кода; Тем не менее могут возникнуть проблемы, поскольку сохраненное определение будет читать CREATE PROCEDURE(три! пробела), и это не соответствует CREATE PROCEDUREни CREATE OR ALTER PROCEDURE… ._.
TheConstructor
@TheConstructor - это то, что я имел в виду в отношении «лишних пробелов». Вы можете обойти это, написав функцию, которая сначала сканирует CREATE, но не внутри комментария, и заменяет ее. В прошлом я этого не делал, но сейчас у меня нет удобного кода функции для публикации. Или, если вы можете гарантировать, что ни в одном из ваших определений объектов не будет комментариев, предшествующих CREATE, игнорируйте проблему с комментариями и просто найдите и замените первый экземпляр CREATE.
Дэвид Спиллетт
Я много раз пробовал этот подход в прошлом, и в целом подход Generate-Scripts был лучше и почти всегда используется сегодня, если только число изменяемых объектов не оказывается относительно небольшим.
RBarryYoung
5

Мне очень нравится ответ Дэвида, и я проголосовал за его программный подход.

Но вы можете попробовать это сегодня для пробного запуска в Azure через SSMS:

Щелкните правой кнопкой мыши по вашей базе данных -> Задачи -> Создать сценарии.

[Back Story] У нас был младший администратор базы данных, который обновил все наши тестовые среды до SQL 2008 R2, в то время как наши производственные среды были на SQL 2008. Это изменение, которое заставляет меня сковывать по сей день. Чтобы перейти к производству, из теста нам нужно было генерировать скрипты в SQL с использованием скриптов генерирования, а в расширенных настройках мы использовали опцию «Тип данных для скрипта: схема и данные» для генерации массивного текстового файла. Мы успешно смогли перенести наши тестовые базы данных R2 на наши устаревшие серверы SQL 2008 - там, где восстановление базы данных на более низкую версию не работало бы. Мы использовали sqlcmd для ввода большого файла - поскольку файлы часто были слишком большими для текстового буфера SSMS.

Я хочу сказать, что этот вариант, вероятно, будет работать и для вас. Вам просто нужно сделать один дополнительный шаг и выполнить поиск и заменить getdate () на [dbo] .getlocaldate в сгенерированном текстовом файле. (Я бы положил вашу функцию в базу данных до миграции, хотя).

(Я никогда не хотел быть опытным в этой помощи при восстановлении базы данных, но какое-то время это стало неэффективным способом выполнения действий. И это работало каждый раз.)

Если вы выберете этот маршрут, убедитесь, что нажмите кнопку «Дополнительно» и выберите все необходимые параметры (прочитайте каждый), чтобы перейти от старой базы данных к новой, например, по умолчанию. Но проведите несколько тестовых прогонов в 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   
KumarHarsh
источник
1

Я проголосовал за ответ Эвана Кэрролла, так как считаю, что это лучшее решение. Я не смог убедить моих коллег в том, что они должны сильно изменить код C #, поэтому мне пришлось использовать код, который написал Дэвид Спиллетт. Я исправил несколько проблем с 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


UDFs
Предложение использовать UDF, которое возвращает сегодняшнюю дату и время, выглядит неплохо, но я думаю, что с UDF все еще достаточно проблем с производительностью, поэтому я решил использовать очень длинное и уродливое решение AT TIME ZONE.

Хенрик Стаун Поулсен
источник