Changing the use of GETDATE() in the entire database
Use the SQL Server tool to export the database objects definition to a SQL file which should include: tables, views, triggers, SPs, functions, and so on
Edit the SQL file (make a backup first) using any text editor that allows you to find the text
"GETDATE()"
and replace it with"[dbo].[getlocaldate]()"
Run the edited SQL file in Azure SQL to create your database objects...
Execute the migration of data.
Here you have a reference from azure documentation: Generating Scripts for SQL Azure
What would be the best way to implement this change?
I would work the other way around. Convert all your timestamps in the database to UTC, and just use UTC and go with the flow. If you need a timestamp in a different tz, you can create a generated column using AT TIME ZONE
(as you did above) that renders the time stamp in that specified TZ (for the app). But, I would seriously consider just having UTC returned to the app, and writing that logic -- the display logic -- in the app.
Rather than export, manually edit, and rerun, you could try do the job directly in the database with something like:
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
of course extending it to deal with functions, triggers, and so forth too.
There are a few caveats:
You may need to be a bit brighter and deal with different/extra white-space between
CREATE
andPROCEDURE
/VIEW
/<other>
. Rather than theREPLACE
for that you might prefer to instead leave theCREATE
in place and execute aDROP
first, but this risks leavingsys.depends
and friends out of kilter whereALTER
may not, also ifALTER
fails you at least have the existing object still in place where withDROP
+CREATE
you may not.If you code has any "clever" smells like modifying its own schema with ad-hoc TSQL then you'll need to make sure the search and replace for
CREATE
->ALTER
doesn't interfere with that.You will be wanting to regression test the entire application(s) after the operation, whether you use the cursor or export+edit+run methods.
I've used this method to make similar schema-wide updates in the past. It is a bit of a hack, and feels quite ugly, but sometimes it is the easiest/quickest way.
Defaults and other constraints can be modified similarly too, though those can only be dropped and recreated rather than altered. Something like:
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
Some more fun that you may need to contend with: if you are partitioning by time then those parts may need altering too. While partitioning on time more granularly then by day is rare you could have issues where DATETIME
s are interpreted by the partitioning function as being the previous or next day depending on timezine, leaving your partitions unaligned with your usual queries.