Initial version of collation migration scripts.

This commit is contained in:
2026-02-05 07:48:34 +01:00
commit 505a9d52a6
6 changed files with 1339 additions and 0 deletions

View File

@@ -0,0 +1,485 @@
/* ============================================================
Compare Script (Schema + Rowcounts + Programmable Objects)
SQL Server 2022
Compares SOURCE and TARGET databases without comparing row contents:
- Rowcount per table (diff list + summary)
- Tables/columns (datatype/length/nullable/collation) diff list + summary
- Programmable objects (views/procs/functions) via SHA2_256 hash of definition
- Triggers via SHA2_256 hash of definition
- Users/Roles existence + role memberships (name-based)
Output:
- Textual progress and summary via RAISERROR ... WITH NOWAIT
- Result sets with detailed diffs
Configure DB names below.
============================================================ */
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @SourceDb sysname = N'sysdb_UTF8';
DECLARE @TargetDb sysname = N'sysdb_utf8_jr';
DECLARE @QSource sysname = QUOTENAME(@SourceDb);
DECLARE @QTarget sysname = QUOTENAME(@TargetDb);
DECLARE @sev int = 10;
DECLARE @Now nvarchar(30);
DECLARE @msg nvarchar(4000);
SET @Now = CONVERT(nvarchar(30), SYSDATETIME(), 121);
SET @msg = CONCAT(@Now, N' | COMPARE_START | Source=', @SourceDb, N' | Target=', @TargetDb);
RAISERROR(@msg, @sev, 1) WITH NOWAIT;
--------------------------------------------------------------------------------
-- 1) Rowcounts per table
--------------------------------------------------------------------------------
SET @Now = CONVERT(nvarchar(30), SYSDATETIME(), 121);
RAISERROR(CONCAT(@Now, N' | STEP 1/5 | Rowcounts per table...'), @sev, 1) WITH NOWAIT;
IF OBJECT_ID('tempdb..#RowCounts') IS NOT NULL DROP TABLE #RowCounts;
CREATE TABLE #RowCounts(
DbName sysname NOT NULL,
SchemaName sysname NOT NULL,
TableName sysname NOT NULL,
RowCount bigint NOT NULL
);
DECLARE @sql nvarchar(max);
SET @sql = N'
INSERT INTO #RowCounts(DbName, SchemaName, TableName, RowCount)
SELECT N''' + REPLACE(@SourceDb,'''','''''') + N''', s.name, t.name, SUM(p.rows)
FROM ' + @QSource + N'.sys.tables t
JOIN ' + @QSource + N'.sys.schemas s ON s.schema_id = t.schema_id
JOIN ' + @QSource + N'.sys.partitions p ON p.object_id = t.object_id AND p.index_id IN (0,1)
WHERE t.is_ms_shipped=0
GROUP BY s.name, t.name;';
EXEC sp_executesql @sql;
SET @sql = N'
INSERT INTO #RowCounts(DbName, SchemaName, TableName, RowCount)
SELECT N''' + REPLACE(@TargetDb,'''','''''') + N''', s.name, t.name, SUM(p.rows)
FROM ' + @QTarget + N'.sys.tables t
JOIN ' + @QTarget + N'.sys.schemas s ON s.schema_id = t.schema_id
JOIN ' + @QTarget + N'.sys.partitions p ON p.object_id = t.object_id AND p.index_id IN (0,1)
WHERE t.is_ms_shipped=0
GROUP BY s.name, t.name;';
EXEC sp_executesql @sql;
;WITH s AS (
SELECT SchemaName, TableName, RowCount FROM #RowCounts WHERE DbName=@SourceDb
),
t AS (
SELECT SchemaName, TableName, RowCount FROM #RowCounts WHERE DbName=@TargetDb
),
d AS (
SELECT
COALESCE(s.SchemaName, t.SchemaName) AS SchemaName,
COALESCE(s.TableName, t.TableName) AS TableName,
s.RowCount AS SourceRows,
t.RowCount AS TargetRows,
(ISNULL(t.RowCount,0) - ISNULL(s.RowCount,0)) AS Diff
FROM s
FULL OUTER JOIN t
ON t.SchemaName = s.SchemaName
AND t.TableName = s.TableName
)
SELECT *
INTO #RowDiff
FROM d
WHERE ISNULL(SourceRows,-1) <> ISNULL(TargetRows,-1);
DECLARE @RowDiffCount int = (SELECT COUNT(*) FROM #RowDiff);
DECLARE @RowTotalSource int = (SELECT COUNT(*) FROM (SELECT DISTINCT SchemaName, TableName FROM #RowCounts WHERE DbName=@SourceDb) x);
DECLARE @RowTotalTarget int = (SELECT COUNT(*) FROM (SELECT DISTINCT SchemaName, TableName FROM #RowCounts WHERE DbName=@TargetDb) x);
SET @Now = CONVERT(nvarchar(30), SYSDATETIME(), 121);
RAISERROR(CONCAT(@Now, N' | ROWCOUNTS | SourceTables=', @RowTotalSource, N' TargetTables=', @RowTotalTarget, N' TablesWithRowDiff=', @RowDiffCount), @sev, 1) WITH NOWAIT;
IF @RowDiffCount = 0
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | ROWCOUNTS_OK | All table rowcounts match.'), @sev, 1) WITH NOWAIT;
ELSE
BEGIN
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | ROWCOUNTS_DIFF | Showing tables with rowcount differences...'), @sev, 1) WITH NOWAIT;
SELECT * FROM #RowDiff ORDER BY ABS(Diff) DESC, SchemaName, TableName;
END
--------------------------------------------------------------------------------
-- 2) Column/schema diffs
--------------------------------------------------------------------------------
SET @Now = CONVERT(nvarchar(30), SYSDATETIME(), 121);
RAISERROR(CONCAT(@Now, N' | STEP 2/5 | Table/column schema diffs...'), @sev, 1) WITH NOWAIT;
IF OBJECT_ID('tempdb..#Cols') IS NOT NULL DROP TABLE #Cols;
CREATE TABLE #Cols(
DbName sysname,
SchemaName sysname,
TableName sysname,
ColumnName sysname,
TypeName sysname,
MaxLen int,
PrecisionVal int,
ScaleVal int,
IsNullable bit,
CollationName sysname NULL
);
SET @sql = N'
INSERT #Cols
SELECT N''' + REPLACE(@SourceDb,'''','''''') + N''',
s.name, t.name, c.name,
ty.name,
c.max_length,
c.precision,
c.scale,
c.is_nullable,
c.collation_name
FROM ' + @QSource + N'.sys.columns c
JOIN ' + @QSource + N'.sys.tables t ON t.object_id=c.object_id
JOIN ' + @QSource + N'.sys.schemas s ON s.schema_id=t.schema_id
JOIN ' + @QSource + N'.sys.types ty ON ty.user_type_id=c.user_type_id
WHERE t.is_ms_shipped=0;';
EXEC sp_executesql @sql;
SET @sql = N'
INSERT #Cols
SELECT N''' + REPLACE(@TargetDb,'''','''''') + N''',
s.name, t.name, c.name,
ty.name,
c.max_length,
c.precision,
c.scale,
c.is_nullable,
c.collation_name
FROM ' + @QTarget + N'.sys.columns c
JOIN ' + @QTarget + N'.sys.tables t ON t.object_id=c.object_id
JOIN ' + @QTarget + N'.sys.schemas s ON s.schema_id=t.schema_id
JOIN ' + @QTarget + N'.sys.types ty ON ty.user_type_id=c.user_type_id
WHERE t.is_ms_shipped=0;';
EXEC sp_executesql @sql;
;WITH s AS (SELECT * FROM #Cols WHERE DbName=@SourceDb),
t AS (SELECT * FROM #Cols WHERE DbName=@TargetDb),
d AS (
SELECT
COALESCE(s.SchemaName, t.SchemaName) AS SchemaName,
COALESCE(s.TableName, t.TableName) AS TableName,
COALESCE(s.ColumnName, t.ColumnName) AS ColumnName,
CONCAT(ISNULL(s.TypeName,N''), N'(', ISNULL(CONVERT(nvarchar(20),s.MaxLen),N''), N')') AS SourceType,
CONCAT(ISNULL(t.TypeName,N''), N'(', ISNULL(CONVERT(nvarchar(20),t.MaxLen),N''), N')') AS TargetType,
s.IsNullable AS SourceNullable,
t.IsNullable AS TargetNullable,
s.CollationName AS SourceCollation,
t.CollationName AS TargetCollation
FROM s
FULL OUTER JOIN t
ON t.SchemaName = s.SchemaName
AND t.TableName = s.TableName
AND t.ColumnName = s.ColumnName
)
SELECT *
INTO #ColDiff
FROM d
WHERE
ISNULL(SourceType,N'') <> ISNULL(TargetType,N'')
OR ISNULL(SourceNullable,2) <> ISNULL(TargetNullable,2)
OR ISNULL(SourceCollation,N'') <> ISNULL(TargetCollation,N'');
DECLARE @ColDiffCount int = (SELECT COUNT(*) FROM #ColDiff);
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | COLUMNS | ColumnDiffs=', @ColDiffCount), @sev, 1) WITH NOWAIT;
IF @ColDiffCount = 0
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | COLUMNS_OK | No column definition diffs detected.'), @sev, 1) WITH NOWAIT;
ELSE
BEGIN
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | COLUMNS_DIFF | Showing column diffs...'), @sev, 1) WITH NOWAIT;
SELECT * FROM #ColDiff ORDER BY SchemaName, TableName, ColumnName;
END
--------------------------------------------------------------------------------
-- 3) Programmable objects
--------------------------------------------------------------------------------
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | STEP 3/5 | Programmable objects (hash compare)...'), @sev, 1) WITH NOWAIT;
IF OBJECT_ID('tempdb..#Modules') IS NOT NULL DROP TABLE #Modules;
CREATE TABLE #Modules(
DbName sysname,
ObjectType nvarchar(60),
SchemaName sysname,
ObjectName sysname,
DefinitionHash varbinary(32) NULL
);
SET @sql = N'
INSERT #Modules
SELECT
N''' + REPLACE(@SourceDb,'''','''''') + N''',
o.type_desc,
s.name,
o.name,
HASHBYTES(''SHA2_256'', CONVERT(varbinary(max), m.definition))
FROM ' + @QSource + N'.sys.objects o
JOIN ' + @QSource + N'.sys.schemas s ON s.schema_id=o.schema_id
JOIN ' + @QSource + N'.sys.sql_modules m ON m.object_id=o.object_id
WHERE o.is_ms_shipped=0
AND o.type IN (''V'',''P'',''FN'',''IF'',''TF'');';
EXEC sp_executesql @sql;
SET @sql = N'
INSERT #Modules
SELECT
N''' + REPLACE(@TargetDb,'''','''''') + N''',
o.type_desc,
s.name,
o.name,
HASHBYTES(''SHA2_256'', CONVERT(varbinary(max), m.definition))
FROM ' + @QTarget + N'.sys.objects o
JOIN ' + @QTarget + N'.sys.schemas s ON s.schema_id=o.schema_id
JOIN ' + @QTarget + N'.sys.sql_modules m ON m.object_id=o.object_id
WHERE o.is_ms_shipped=0
AND o.type IN (''V'',''P'',''FN'',''IF'',''TF'');';
EXEC sp_executesql @sql;
;WITH s AS (SELECT * FROM #Modules WHERE DbName=@SourceDb),
t AS (SELECT * FROM #Modules WHERE DbName=@TargetDb),
d AS (
SELECT
COALESCE(s.ObjectType, t.ObjectType) AS ObjectType,
COALESCE(s.SchemaName, t.SchemaName) AS SchemaName,
COALESCE(s.ObjectName, t.ObjectName) AS ObjectName,
CASE WHEN s.ObjectName IS NULL THEN 'MISSING_IN_SOURCE'
WHEN t.ObjectName IS NULL THEN 'MISSING_IN_TARGET'
WHEN s.DefinitionHash <> t.DefinitionHash THEN 'DIFFERENT_DEFINITION'
ELSE 'OK' END AS Status
FROM s
FULL OUTER JOIN t
ON t.ObjectType = s.ObjectType
AND t.SchemaName = s.SchemaName
AND t.ObjectName = s.ObjectName
)
SELECT *
INTO #ModuleDiff
FROM d
WHERE Status <> 'OK';
DECLARE @ModuleDiffCount int = (SELECT COUNT(*) FROM #ModuleDiff);
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | MODULES | Differences=', @ModuleDiffCount), @sev, 1) WITH NOWAIT;
IF @ModuleDiffCount = 0
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | MODULES_OK | No module definition diffs detected.'), @sev, 1) WITH NOWAIT;
ELSE
BEGIN
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | MODULES_DIFF | Showing module diffs...'), @sev, 1) WITH NOWAIT;
SELECT * FROM #ModuleDiff ORDER BY ObjectType, SchemaName, ObjectName;
END
--------------------------------------------------------------------------------
-- 4) Triggers
--------------------------------------------------------------------------------
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | STEP 4/5 | Triggers (hash compare)...'), @sev, 1) WITH NOWAIT;
IF OBJECT_ID('tempdb..#Triggers') IS NOT NULL DROP TABLE #Triggers;
CREATE TABLE #Triggers(
DbName sysname,
TriggerSchema sysname,
TriggerName sysname,
ParentSchema sysname,
ParentName sysname,
DefinitionHash varbinary(32) NULL
);
SET @sql = N'
INSERT #Triggers
SELECT
N''' + REPLACE(@SourceDb,'''','''''') + N''',
ss.name,
tr.name,
ps.name,
pt.name,
HASHBYTES(''SHA2_256'', CONVERT(varbinary(max), m.definition))
FROM ' + @QSource + N'.sys.triggers tr
JOIN ' + @QSource + N'.sys.tables pt ON pt.object_id = tr.parent_id
JOIN ' + @QSource + N'.sys.schemas ps ON ps.schema_id = pt.schema_id
JOIN ' + @QSource + N'.sys.schemas ss ON ss.schema_id = tr.schema_id
JOIN ' + @QSource + N'.sys.sql_modules m ON m.object_id = tr.object_id
WHERE tr.is_ms_shipped=0;';
EXEC sp_executesql @sql;
SET @sql = N'
INSERT #Triggers
SELECT
N''' + REPLACE(@TargetDb,'''','''''') + N''',
ss.name,
tr.name,
ps.name,
pt.name,
HASHBYTES(''SHA2_256'', CONVERT(varbinary(max), m.definition))
FROM ' + @QTarget + N'.sys.triggers tr
JOIN ' + @QTarget + N'.sys.tables pt ON pt.object_id = tr.parent_id
JOIN ' + @QTarget + N'.sys.schemas ps ON ps.schema_id = pt.schema_id
JOIN ' + @QTarget + N'.sys.schemas ss ON ss.schema_id = tr.schema_id
JOIN ' + @QTarget + N'.sys.sql_modules m ON m.object_id = tr.object_id
WHERE tr.is_ms_shipped=0;';
EXEC sp_executesql @sql;
;WITH s AS (SELECT * FROM #Triggers WHERE DbName=@SourceDb),
t AS (SELECT * FROM #Triggers WHERE DbName=@TargetDb),
d AS (
SELECT
COALESCE(s.TriggerSchema, t.TriggerSchema) AS TriggerSchema,
COALESCE(s.TriggerName, t.TriggerName) AS TriggerName,
COALESCE(s.ParentSchema, t.ParentSchema) AS ParentSchema,
COALESCE(s.ParentName, t.ParentName) AS ParentTable,
CASE WHEN s.TriggerName IS NULL THEN 'MISSING_IN_SOURCE'
WHEN t.TriggerName IS NULL THEN 'MISSING_IN_TARGET'
WHEN s.DefinitionHash <> t.DefinitionHash THEN 'DIFFERENT_DEFINITION'
ELSE 'OK' END AS Status
FROM s
FULL OUTER JOIN t
ON t.TriggerSchema = s.TriggerSchema
AND t.TriggerName = s.TriggerName
AND t.ParentSchema = s.ParentSchema
AND t.ParentName = s.ParentName
)
SELECT *
INTO #TriggerDiff
FROM d
WHERE Status <> 'OK';
DECLARE @TriggerDiffCount int = (SELECT COUNT(*) FROM #TriggerDiff);
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | TRIGGERS | Differences=', @TriggerDiffCount), @sev, 1) WITH NOWAIT;
IF @TriggerDiffCount = 0
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | TRIGGERS_OK | No trigger definition diffs detected.'), @sev, 1) WITH NOWAIT;
ELSE
BEGIN
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | TRIGGERS_DIFF | Showing trigger diffs...'), @sev, 1) WITH NOWAIT;
SELECT * FROM #TriggerDiff ORDER BY TriggerSchema, TriggerName, ParentSchema, ParentTable;
END
--------------------------------------------------------------------------------
-- 5) Users/Roles
--------------------------------------------------------------------------------
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | STEP 5/5 | Users/Roles (existence + memberships)...'), @sev, 1) WITH NOWAIT;
IF OBJECT_ID('tempdb..#Principals') IS NOT NULL DROP TABLE #Principals;
CREATE TABLE #Principals(
DbName sysname,
Name sysname,
TypeDesc nvarchar(60)
);
SET @sql = N'
INSERT #Principals
SELECT N''' + REPLACE(@SourceDb,'''','''''') + N''', name, type_desc
FROM ' + @QSource + N'.sys.database_principals
WHERE type IN (''S'',''U'',''G'',''R'')
AND name NOT IN (''dbo'',''guest'',''INFORMATION_SCHEMA'',''sys'');';
EXEC sp_executesql @sql;
SET @sql = N'
INSERT #Principals
SELECT N''' + REPLACE(@TargetDb,'''','''''') + N''', name, type_desc
FROM ' + @QTarget + N'.sys.database_principals
WHERE type IN (''S'',''U'',''G'',''R'')
AND name NOT IN (''dbo'',''guest'',''INFORMATION_SCHEMA'',''sys'');';
EXEC sp_executesql @sql;
;WITH s AS (SELECT * FROM #Principals WHERE DbName=@SourceDb),
t AS (SELECT * FROM #Principals WHERE DbName=@TargetDb),
d AS (
SELECT
COALESCE(s.Name, t.Name) AS PrincipalName,
s.TypeDesc AS SourceType,
t.TypeDesc AS TargetType,
CASE WHEN s.Name IS NULL THEN 'MISSING_IN_SOURCE'
WHEN t.Name IS NULL THEN 'MISSING_IN_TARGET'
WHEN s.TypeDesc <> t.TypeDesc THEN 'TYPE_DIFF'
ELSE 'OK' END AS Status
FROM s
FULL OUTER JOIN t ON t.Name = s.Name
)
SELECT *
INTO #PrincipalDiff
FROM d
WHERE Status <> 'OK';
DECLARE @PrincipalDiffCount int = (SELECT COUNT(*) FROM #PrincipalDiff);
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | PRINCIPALS | Differences=', @PrincipalDiffCount), @sev, 1) WITH NOWAIT;
IF @PrincipalDiffCount > 0
BEGIN
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | PRINCIPALS_DIFF | Showing principal diffs...'), @sev, 1) WITH NOWAIT;
SELECT * FROM #PrincipalDiff ORDER BY PrincipalName;
END
ELSE
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | PRINCIPALS_OK | No principal diffs detected.'), @sev, 1) WITH NOWAIT;
IF OBJECT_ID('tempdb..#RoleMembers') IS NOT NULL DROP TABLE #RoleMembers;
CREATE TABLE #RoleMembers(
DbName sysname,
RoleName sysname,
MemberName sysname
);
SET @sql = N'
INSERT #RoleMembers
SELECT N''' + REPLACE(@SourceDb,'''','''''') + N''',
r.name AS RoleName, m.name AS MemberName
FROM ' + @QSource + N'.sys.database_role_members rm
JOIN ' + @QSource + N'.sys.database_principals r ON r.principal_id = rm.role_principal_id
JOIN ' + @QSource + N'.sys.database_principals m ON m.principal_id = rm.member_principal_id
WHERE r.name NOT IN (''public'');';
EXEC sp_executesql @sql;
SET @sql = N'
INSERT #RoleMembers
SELECT N''' + REPLACE(@TargetDb,'''','''''') + N''',
r.name AS RoleName, m.name AS MemberName
FROM ' + @QTarget + N'.sys.database_role_members rm
JOIN ' + @QTarget + N'.sys.database_principals r ON r.principal_id = rm.role_principal_id
JOIN ' + @QTarget + N'.sys.database_principals m ON m.principal_id = rm.member_principal_id
WHERE r.name NOT IN (''public'');';
EXEC sp_executesql @sql;
;WITH s AS (SELECT RoleName, MemberName FROM #RoleMembers WHERE DbName=@SourceDb),
t AS (SELECT RoleName, MemberName FROM #RoleMembers WHERE DbName=@TargetDb),
d AS (
SELECT
COALESCE(s.RoleName, t.RoleName) AS RoleName,
COALESCE(s.MemberName, t.MemberName) AS MemberName,
CASE WHEN s.RoleName IS NULL THEN 'MISSING_IN_SOURCE'
WHEN t.RoleName IS NULL THEN 'MISSING_IN_TARGET'
ELSE 'OK' END AS Status
FROM s
FULL OUTER JOIN t
ON t.RoleName = s.RoleName AND t.MemberName = s.MemberName
)
SELECT *
INTO #RoleMemberDiff
FROM d
WHERE Status <> 'OK';
DECLARE @RoleMemberDiffCount int = (SELECT COUNT(*) FROM #RoleMemberDiff);
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | ROLEMEMBERS | Differences=', @RoleMemberDiffCount), @sev, 1) WITH NOWAIT;
IF @RoleMemberDiffCount > 0
BEGIN
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | ROLEMEMBERS_DIFF | Showing role membership diffs...'), @sev, 1) WITH NOWAIT;
SELECT * FROM #RoleMemberDiff ORDER BY RoleName, MemberName;
END
ELSE
RAISERROR(CONCAT(CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | ROLEMEMBERS_OK | No role membership diffs detected.'), @sev, 1) WITH NOWAIT;
RAISERROR(CONCAT(
CONVERT(nvarchar(30), SYSDATETIME(), 121), N' | COMPARE_DONE | ',
N'RowDiffTables=', @RowDiffCount,
N' | ColumnDiffs=', @ColDiffCount,
N' | ModuleDiffs=', @ModuleDiffCount,
N' | TriggerDiffs=', @TriggerDiffCount,
N' | PrincipalDiffs=', @PrincipalDiffCount,
N' | RoleMemberDiffs=', @RoleMemberDiffCount
), @sev, 1) WITH NOWAIT;