Зміна використання 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, щоб експортувати визначення об'єктів бази даних у файл SQL, який повинен включати: таблиці, представлення, тригери, SP-адреси, функції тощо

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

  3. Запустіть відредагований файл SQL в Azure SQL для створення об’єктів бази даних ...

  4. Виконати міграцію даних.

Тут ви маєте посилання з документації на azure: Створення скриптів для SQL Azure


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

15

Який найкращий спосіб здійснити цю зміну?

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


якщо це була лише база даних, я можу це врахувати, але ця зміна впливає на безліч інших додатків та програмного забезпечення, які потребують серйозного рефакторингу. Тож, на жаль, це не вибір для мене
Ламак

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

звичайно, розширюючи його і на функції, тригери тощо.

Є кілька застережень:

  • Можливо, вам знадобиться бути трохи яскравішим і мати справу з різними / додатковими пробілами між CREATEта PROCEDURE/ VIEW/ <other>. Замість того, REPLACEщоб ви могли замість цього залишити CREATEмісце на місці та виконати DROPперше виконання, але це ризикує залишити sys.dependsта друзів позабитий там, де ALTERце не можливо, також, якщо ALTERви не зможете, принаймні, наявний об’єкт все ще знаходиться на місці, де з DROP+ CREATEви можете ні.

  • Якщо в коді є якісь "розумні" запахи, як зміна власної схеми за допомогою спеціального TSQL, вам потрібно переконатися, що пошук і заміна на CREATE-> ALTERне перешкоджають цьому.

  • Вам потрібно буде перевірити регресію всієї програми (програм) після операції, незалежно від того, використовуєте ви курсор або експортуєте + редагувати + запускати методи.

У минулому я використовував цей метод, щоб робити подібні оновлення для всіх схем. Це трохи хак, і відчуває себе досить потворно, але іноді це найпростіший / швидкий спосіб.

Значення за замовчуванням та інші обмеження також можуть бути змінені аналогічно, хоча вони можуть бути виключені та відтворені, а не змінені. Щось на зразок:

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функція секціонування інтерпретується як попередній або наступний день залежно від timezine, залишаючи ваші розділи не узгодженими із вашими звичайними запитами.


так, застереження - це те, що робить це важким. Крім того, це не враховує значення за замовчуванням стовпця. Все одно
дякую

Значення за замовчуванням стовпця та інші обмеження також можна відсканувати на sysсхемі та програмно модифікувати.
Девід Спіллетт

Можливо, заміна на, наприклад, CREATE OR ALTER PROCEDUREдопомагає вирішити деякі проблеми генерації коду; все ж можуть виникнути проблеми, коли збережене визначення буде прочитати CREATE PROCEDURE(три! пробіли), і це не відповідає CREATE PROCEDUREні CREATE OR ALTER PROCEDURE… ._.
TheConstructor

@TheConstructor - саме так я мав на увазі wrt "додатковий пробіл". Ви можете це обійти, написавши функцію, яка сканує першу, CREATEяка не знаходиться в коментарі, і замінює її. Я раніше цього / подібного не робив, але зараз не маю зручності в коді функції для публікації. Або якщо ви можете гарантувати, що жодне з ваших об’єктних визначень не має коментарів CREATE, ігноруйте питання коментарів і просто знайдіть і замініть перший екземпляр CREATE.
Девід Спіллетт

Я багато разів пробував цей підхід в минулому, і на балансі підхід «Створити сценарії» був кращим і майже завжди є тим, що я використовую сьогодні, якщо кількість об'єктів, які потрібно змінити, виявляється порівняно невеликою.
RBarryYoung

5

Мені дуже подобається відповідь Давида і наголосив це на програмному способі ведення справ.

Але ви можете спробувати це сьогодні для тестового запуску в Azure через SSMS:

Клацніть правою кнопкою миші свою базу даних -> Завдання -> Створення скриптів ..

[Назад Історія] у нас був молодший DBA, який оновив усі наші тестові середовища до 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   

1

Я підтримав відповідь Евана Керролла, оскільки вважаю, що це найкраще рішення. Мені не вдалося переконати своїх колег, що вони повинні змінити багато C #-коду, тому мені довелося використовувати код, який написав Девід Спіллет. Я виправив декілька проблем з 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


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.