/* ============================================================================ 时间口径迁移:本地墙钟(服务器时间)→ UTC 墙钟 ---------------------------------------------------------------------------- 背景:本项目原先全链路使用「服务器本地墙钟值」(库端 getdate() / sysdatetime(), 应用层 LocalDateTime.now(Asia/Shanghai))。现统一为 UTC 口径(见后端 TimeUtils): · 库中 datetime 列一律存 UTC 墙钟值,新写入默认值走 sysutcdatetime(); · 接口出口发带 Z 的 ISO 串("2026-09-26T02:00:00Z"),前端按浏览器时区展示; · 提交时前端带 Z / ±HH:mm,后端折回 UTC 墙钟值落库。 本脚本做两件事: 1. 默认值约束:getdate() / sysdatetime() → sysutcdatetime(); 2. 存量数据:把每个 datetime 列的旧值从「来源时区墙钟」折成 UTC。 幂等:执行成功后在 dbo.s_time_utc_migrated 留标记,重复执行直接跳过 (校正类脚本重复执行会二次偏移,必须防重)。 执行范围:每个机构库各执行一次(后端按机构切换连接库)。 时区前提:旧数据是「中国标准时间(UTC+8)」的墙钟值 —— 现网服务器与使用方均在中国。 若某机构库的旧值来自别的时区,把 @source_zone 换成对应 Windows 时区名即可。 依赖:AT TIME ZONE 需要 SQL Server 2016+;建议在维护窗口执行(全表更新会持有行锁)。 ============================================================================ */ set nocount on; go /* --------------------------------------------------------------------------- 0. 防重标记表(首次执行时创建) --------------------------------------------------------------------------- */ if object_id(N'dbo.s_time_utc_migrated', N'U') is null begin create table dbo.s_time_utc_migrated ( b_id varchar(50) not null primary key, -- 迁移标识 b_source_zone sysname not null, -- 旧值的来源时区 b_migrated_at datetime2 not null, -- 执行时间(UTC) b_column_count int not null -- 校正的列数 ); end go declare @source_zone sysname = N'China Standard Time'; declare @already int = (select count(*) from dbo.s_time_utc_migrated where b_id = 'time-utc'); if @already > 0 begin print N'[跳过] 时间口径迁移已执行过(见 dbo.s_time_utc_migrated 的 time-utc 标记)。'; end else begin declare @table sysname, @column sysname, @type sysname, @sql nvarchar(max); /* ----------------------------------------------------------------------- 1. 默认值约束:getdate() / sysdatetime() → sysutcdatetime() ----------------------------------------------------------------------- */ declare @constraint sysname; declare defaults_cursor cursor local fast_forward for select dc.name, t.name, c.name from sys.default_constraints dc join sys.tables t on t.object_id = dc.parent_object_id join sys.columns c on c.object_id = dc.parent_object_id and c.column_id = dc.parent_column_id where t.is_ms_shipped = 0 and (dc.definition like '%getdate()%' or dc.definition like '%sysdatetime()%') and dc.definition not like '%sysutcdatetime%'; open defaults_cursor; fetch next from defaults_cursor into @constraint, @table, @column; while @@fetch_status = 0 begin set @sql = N'alter table dbo.' + quotename(@table) + N' drop constraint ' + quotename(@constraint) + N';' + N'alter table dbo.' + quotename(@table) + N' add constraint ' + quotename(@constraint) + N' default sysutcdatetime() for ' + quotename(@column) + N';'; execute sp_executesql @sql; print N'[默认值] ' + @table + N'.' + @column + N' → sysutcdatetime()'; fetch next from defaults_cursor into @constraint, @table, @column; end close defaults_cursor; deallocate defaults_cursor; /* ----------------------------------------------------------------------- 2. 存量数据:每个 datetime 列从「@source_zone 墙钟」折成 UTC AT TIME ZONE 先给旧值贴上来源时区,再换算到 UTC —— 不写死 -8, 将来某个库的旧值来自其他时区时改 @source_zone 即可。 ----------------------------------------------------------------------- */ declare @column_count int = 0; declare time_columns cursor local fast_forward for select t.name, c.name, ty.name from sys.columns c join sys.tables t on t.object_id = c.object_id join sys.schemas s on s.schema_id = t.schema_id join sys.types ty on ty.user_type_id = c.user_type_id where s.name = 'dbo' and t.is_ms_shipped = 0 and c.is_computed = 0 and t.name <> 's_time_utc_migrated' and ty.name in ('datetime', 'datetime2', 'smalldatetime') order by t.name, c.column_id; open time_columns; fetch next from time_columns into @table, @column, @type; while @@fetch_status = 0 begin set @sql = N'update dbo.' + quotename(@table) + N' set ' + quotename(@column) + N' = cast(((' + quotename(@column) + N' at time zone @zone) at time zone ''UTC'') as ' + @type + N')' + N' where ' + quotename(@column) + N' is not null;'; execute sp_executesql @sql, N'@zone sysname', @zone = @source_zone; set @column_count = @column_count + 1; fetch next from time_columns into @table, @column, @type; end close time_columns; deallocate time_columns; /* ----------------------------------------------------------------------- 3. 留痕 ----------------------------------------------------------------------- */ insert into dbo.s_time_utc_migrated (b_id, b_source_zone, b_migrated_at, b_column_count) values ('time-utc', @source_zone, sysutcdatetime(), @column_count); print N'[完成] 已按 ' + @source_zone + N' 校正 ' + cast(@column_count as varchar(10)) + N' 个时间列;抽查 b_created_at / b_occurdatetime 与界面展示是否一致。'; end go