You are accessing the Members Only area for DynDeveloper.com using guest access. The member experience is slightly different
stevegray
4Penny.net
Points: 55509

9/24/2017 3:37:03 PM

How to change the DB Collation for a Dynamics GP server

* This article, and all our great .NET Development documentation, Is available on the .NET Development menu

BACK UP YOUR DATABASE FIRST

This article represents several days work. I'm sure that you won't want to read through the code, but read enough that you'll remember where you saw it. That way when you need it a year from now you'll remember where you saw it.

This week I encountered a Dynamics GP SQL server set to 'BINARY' collation. I tried to use it but as I loaded my library code onto the box to do an integration, none of it was running. The stored procedures wouldn't even create. Grrrr.

The resulting solution is below. There are three basic steps:

  • Set the SERVER collation
  • Set the DATABASE collation
  • Set the TABLE FIELD collation (each text field in each table is assigned its own collation)

I didn't need to do the first, the consultant on the job had already fixed the SERVER collation; so the code below does not do that. But you'll need to do that first.

So, my task was to set the DATABASE and FIELD collation. There is a method that I see described that involves using BCP to export all the data, then fixing the install and then BCPing the data back in. I came across this solution before I found help with the BCP method, so that's what I used.

I got the method (and most of the code) from

http://sqlmag.com/database-performance-tuning/seven-step-process-changing-database-collation

I fine tuned it for use with Dynamics GP, and my resulting code is easier to use for a GP db. But props to SQLMAG, their code is great, and the concept came from them.

The work is not in the 'change collation' commands, they're easy. The work is in scripting all the indexes, constraints, views, and table valued functions that need to be dropped and recreated to be able to run the 'change collation' commands.

One final note. The code below consists of the first main script and a number of following scripts that handle various 'drop and recreate' tasks. The scripts are numbered 020, 030, 040, etc. They each produce two resulting scripts: a drop script and a create script. I named all those '020 drop' and '020 recreate', etc. Make sense?

Ready? Here we go. HTH.

 

4Penny.net
Version: All
Section: SQL Scripts

 

 
--make sure the db is in good shape
DBCC CHECKDB WITH DATA_PURITY
 
--running the 'alter database' (below) showed these errors, fix them *** YOU WILL PROBABLY NOT NEED THIS, IT IS A LOCAL ISSUE
SELECT * From sys.objects where upper(name) = 'SEEGLGROSSPROFITYTDMETRICMULTICOCALLER'
drop proc seeGLGrossProfitYTDMetricMultiCOCaller
SELECT * From sys.objects where upper(name) = 'SEETOTALPURCHASESMETRICMULTICOCALLER'
drop proc seeTotalPurchasesMetricMultiCoCaller
 
--run 020 to script all the constraints. Drop them now, we'll recreate later
 
--run 035 to script dropping and recreating views
 
--run 025 to script all table valued functions
 
--this finally worked
ALTER DATABASE mbi COLLATE SQL_Latin1_General_CP1_CI_AS
 
 
--now, the DATABASE collation is changed
 
--run 040 sp_DropAndCreateIndexes
 
--run 050 Disable Change Tracking. Warning: I never turn this on (because I don't care :)   )
 
--run 060 drop and recreate foriegn key constraints
 
--run 070 DropOrCreatePrimaryIndexes
 
--run 080 Fix field collations
--the above script gave me one odd constraint error, I manually scripted and dropped the collation in the '090' script
 
--run 090 to put back the odd constraint
 
--run 070 to put the foriegn key indexes back
 
--run 060 to put the foriegn key constraints back
 
--run 020 to put the constraints back

--run 025 to put the functions back

--run 035 to put the views back

020 Drop and Create CK constraints

SELECT
        'ALTER TABLE  ' + QuoteName(OBJECT_NAME(so.parent_obj)) + ' DROP CONSTRAINT ' +     QuoteName(CONSTRAINT_NAME)
    FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc
        INNER JOIN sys.sysobjects so     ON upper(cc.CONSTRAINT_NAME) = upper(so.[name])
    order by cc.CONSTRAINT_NAME
 
SELECT
        'ALTER TABLE  ' + QuoteName(OBJECT_NAME(so.parent_obj)) + ' ADD CONSTRAINT ' + QuoteName(CONSTRAINT_NAME) + ' CHECK ' + CHECK_CLAUSE
    FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc
        INNER JOIN sys.sysobjects so ON upper(cc.CONSTRAINT_NAME) = upper(so.[name])
    order by cc.CONSTRAINT_NAME

025 drop and create table valued functions

SELECT 'DROP FUNCTION ' + SPECIFIC_NAME from INFORMATION_SCHEMA.ROUTINES where ROUTINE_TYPE = 'FUNCTION' and DATA_TYPE = 'TABLE' order by upper(specific_NAME)
 
 
SELECT m.definition + char(13) + char(10) + 'GO'
    FROM sys.sql_modules AS m
        INNER JOIN sys.objects AS o ON m.[object_id] = o.[object_id]
        WHERE o.type IN ('TF');

035 Drop and Recreate views

DECLARE JOB_CURSOR CURSOR FOR
SELECT OBJECT_ID FROM SYS.OBJECTS WHERE [TYPE] IN ('v')
OPEN JOB_CURSOR
DECLARE @OBJECT_ID AS INT
FETCH NEXT FROM JOB_CURSOR
INTO @OBJECT_ID
 
DECLARE @SQL AS VARCHAR(MAX)
 
   WHILE @@FETCH_STATUS = 0
    BEGIN
         
        SELECT @SQL = 'IF OBJECT_ID (''['+ S.NAME + '].['+ O.NAME + ']'') IS NOT NULL BEGIN DROP ' + CASE O.TYPE
            WHEN 'P' THEN 'PROCEDURE '
            WHEN 'V' THEN 'VIEW '
        END + ' ['+ S.NAME + '].['+ O.NAME + ']' + ' end'
                        FROM SYS.OBJECTS O
                        INNER JOIN SYS.SQL_MODULES M ON O.OBJECT_ID = M.OBJECT_ID
                        INNER JOIN SYS.SCHEMAS S ON S.SCHEMA_ID = O.SCHEMA_ID
                        WHERE O.OBJECT_ID = @OBJECT_ID
 
 
        PRINT @SQL
 
     FETCH NEXT FROM JOB_CURSOR
     INTO @OBJECT_ID
    END
 
CLOSE JOB_CURSOR
DEALLOCATE JOB_CURSOR
 
print ''
print ''
 
DECLARE JOB_CURSOR CURSOR FOR
SELECT OBJECT_ID FROM SYS.OBJECTS WHERE [TYPE] IN ('v')
OPEN JOB_CURSOR
 
FETCH NEXT FROM JOB_CURSOR
INTO @OBJECT_ID
 
   WHILE @@FETCH_STATUS = 0
    BEGIN
         
        SELECT @SQL = M.DEFINITION  + 'GO'  + char(13) + char(10)
                        FROM SYS.OBJECTS O
                        INNER JOIN SYS.SQL_MODULES M ON O.OBJECT_ID = M.OBJECT_ID
                        INNER JOIN SYS.SCHEMAS S ON S.SCHEMA_ID = O.SCHEMA_ID
                        WHERE O.OBJECT_ID = @OBJECT_ID
         
        PRINT @SQL
 
     FETCH NEXT FROM JOB_CURSOR
     INTO @OBJECT_ID
    END
 
CLOSE JOB_CURSOR
DEALLOCATE JOB_CURSOR

040 sp_DropAndCreateIndexes.sql

DECLARE @SchemaName VARCHAR(256)
DECLARE @TableName VARCHAR(256)
DECLARE @IndexName VARCHAR(256)
DECLARE @TSQLDropIndex VARCHAR(MAX)
 
DECLARE CursorIndexes CURSOR FOR
    SELECT schema_name(t.schema_id), t.name,  i.name
        FROM sys.indexes i
            INNER JOIN sys.tables t ON t.object_id= i.object_id
        WHERE i.type>0 and t.is_ms_shipped=0 and t.name<>'sysdiagrams'
            and (is_primary_key=0 and is_unique_constraint=0)
        order by t.name
 
OPEN CursorIndexes
FETCH NEXT FROM CursorIndexes INTO @SchemaName,@TableName,@IndexName
 
WHILE @@fetch_status = 0
BEGIN
 SET @TSQLDropIndex = 'DROP INDEX '+QUOTENAME(@SchemaName)+ '.' + QUOTENAME(@TableName) + '.' +QUOTENAME(@IndexName)
 PRINT @TSQLDropIndex
 FETCH NEXT FROM CursorIndexes INTO @SchemaName,@TableName,@IndexName
END
 
CLOSE CursorIndexes
DEALLOCATE CursorIndexes
 
 
--declare @SchemaName varchar(100)
--declare @TableName varchar(256)
--declare @IndexName varchar(256)
declare @ColumnName varchar(100)
declare @is_unique varchar(100)
declare @IndexTypeDesc varchar(100)
declare @FileGroupName varchar(100)
declare @is_disabled varchar(100)
declare @IndexOptions varchar(max)
declare @IndexColumnId int
declare @IsDescendingKey int
declare @IsIncludedColumn int
declare @TSQLScripCreationIndex varchar(max)
declare @TSQLScripDisableIndex varchar(max)
 
declare CursorIndex cursor for
    select
            schema_name(t.schema_id) [schema_name], t.name, ix.name, --SchemaName, @TableName, @IndexName
            case when ix.is_unique = 1 then 'UNIQUE ' else '' END -- @is_unique
            , ix.type_desc,
            case when ix.is_padded=1 then 'PAD_INDEX = ON, ' else 'PAD_INDEX = OFF, ' end
            + case when ix.allow_page_locks=1 then 'ALLOW_PAGE_LOCKS = ON, ' else 'ALLOW_PAGE_LOCKS = OFF, ' end
            + case when ix.allow_row_locks=1 then  'ALLOW_ROW_LOCKS = ON, ' else 'ALLOW_ROW_LOCKS = OFF, ' end
            + case when INDEXPROPERTY(t.object_id, ix.name, 'IsStatistics') = 1 then 'STATISTICS_NORECOMPUTE = ON, ' else 'STATISTICS_NORECOMPUTE = OFF, ' end
            + case when ix.ignore_dup_key=1 then 'IGNORE_DUP_KEY = ON, ' else 'IGNORE_DUP_KEY = OFF, ' end
            + 'SORT_IN_TEMPDB = OFF'  AS IndexOptions
            , ix.is_disabled , FILEGROUP_NAME(ix.data_space_id) FileGroupName
        from sys.tables t
            inner join sys.indexes ix on t.object_id=ix.object_id
        where ix.type>0 and ix.is_primary_key=0 and ix.is_unique_constraint=0 --and schema_name(tb.schema_id)= @SchemaName and tb.name=@TableName
            and t.is_ms_shipped=0 and t.name<>'sysdiagrams'
        order by t.name, schema_name(t.schema_id), ix.name
 
open CursorIndex
fetch next from CursorIndex into  @SchemaName, @TableName, @IndexName, @is_unique, @IndexTypeDesc, @IndexOptions,@is_disabled, @FileGroupName
 
while (@@fetch_status=0)
begin
    declare @IndexColumns varchar(max)
    declare @IncludedColumns varchar(max)
  
    set @IndexColumns=''
    set @IncludedColumns=''
  
    --================================================================================
    --inner cursor
    --================================================================================
    declare CursorIndexColumn cursor for
        select col.name, ixc.is_descending_key, ixc.is_included_column
            from sys.tables tb
                inner join sys.indexes ix on tb.object_id=ix.object_id
                inner join sys.index_columns ixc on ix.object_id=ixc.object_id and ix.index_id= ixc.index_id
                inner join sys.columns col on ixc.object_id =col.object_id  and ixc.column_id=col.column_id
            where ix.type>0 and (ix.is_primary_key=0 or ix.is_unique_constraint=0)
                and schema_name(tb.schema_id)=@SchemaName and tb.name=@TableName and ix.name=@IndexName
            order by ixc.index_column_id
  
    open CursorIndexColumn
    fetch next from CursorIndexColumn into  @ColumnName, @IsDescendingKey, @IsIncludedColumn
  
    while (@@fetch_status=0) begin
        if @IsIncludedColumn=0
            set @IndexColumns=@IndexColumns + @ColumnName  + case when @IsDescendingKey=1  then ' DESC, ' else  ' ASC, ' end
        else
            set @IncludedColumns=@IncludedColumns  + @ColumnName  +', '
 
        fetch next from CursorIndexColumn into @ColumnName, @IsDescendingKey, @IsIncludedColumn
    end
 
    close CursorIndexColumn
    deallocate CursorIndexColumn
    --================================================================================
    --end inner cursor
    --================================================================================
 
    set @IndexColumns = substring(@IndexColumns, 1, len(@IndexColumns)-1)
    set @IncludedColumns = case when len(@IncludedColumns) >0 then substring(@IncludedColumns, 1, len(@IncludedColumns)-1) else '' end
    --  print @IndexColumns
    --  print @IncludedColumns
 
    set @TSQLScripCreationIndex = ''
    set @TSQLScripDisableIndex = ''
 
    set @TSQLScripCreationIndex = 'CREATE '+ @is_unique  + @IndexTypeDesc + ' INDEX ' + QUOTENAME(@IndexName) + ' ON ' + QUOTENAME(@SchemaName) + '.' + QUOTENAME(@TableName) + '('+@IndexColumns+') '+
        case when len(@IncludedColumns) > 0 then ' INCLUDE (' + @IncludedColumns+ ')' else '' end ' WITH (' + @IndexOptions+ ') ON ' + QUOTENAME(@FileGroupName) + ';' 
 
    print @TSQLScripCreationIndex
 
    if @is_disabled=1 begin
        set  @TSQLScripDisableIndex=  CHAR(13) + char(10) + 'ALTER INDEX ' +QUOTENAME(@IndexName) + ' ON ' + QUOTENAME(@SchemaName) +'.'+ QUOTENAME(@TableName) + ' DISABLE;' + CHAR(13)
        print @TSQLScripDisableIndex
    end
 
    fetch next from CursorIndex into  @SchemaName, @TableName, @IndexName, @is_unique, @IndexTypeDesc, @IndexOptions,@is_disabled, @FileGroupName
 
end
close CursorIndex
deallocate CursorIndex

060 drop and recreate foriegn key constraints

 
SELECT N'
ALTER TABLE ' + QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name)
    + ' DROP CONSTRAINT ' + QUOTENAME(fk.name) + ';'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS ct
  ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs
  ON ct.[schema_id] = cs.[schema_id];
 
 
SELECT N'
ALTER TABLE '
   + QUOTENAME(cs.name) + '.' + QUOTENAME(ct.name)
   + ' ADD CONSTRAINT ' + QUOTENAME(fk.name)
   + ' FOREIGN KEY (' + STUFF((SELECT ',' + QUOTENAME(c.name)
   -- get all the columns in the constraint table
    FROM sys.columns AS c
    INNER JOIN sys.foreign_key_columns AS fkc
    ON fkc.parent_column_id = c.column_id
    AND fkc.parent_object_id = c.[object_id]
    WHERE fkc.constraint_object_id = fk.[object_id]
    ORDER BY fkc.constraint_column_id
    FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'')
  + ') REFERENCES ' + QUOTENAME(rs.name) + '.' + QUOTENAME(rt.name)
  + '(' + STUFF((SELECT ',' + QUOTENAME(c.name)
   -- get all the referenced columns
    FROM sys.columns AS c
    INNER JOIN sys.foreign_key_columns AS fkc
    ON fkc.referenced_column_id = c.column_id
    AND fkc.referenced_object_id = c.[object_id]
    WHERE fkc.constraint_object_id = fk.[object_id]
    ORDER BY fkc.constraint_column_id
    FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') + ');'
FROM sys.foreign_keys AS fk
INNER JOIN sys.tables AS rt -- referenced table
  ON fk.referenced_object_id = rt.[object_id]
INNER JOIN sys.schemas AS rs
  ON rt.[schema_id] = rs.[schema_id]
INNER JOIN sys.tables AS ct -- constraint table
  ON fk.parent_object_id = ct.[object_id]
INNER JOIN sys.schemas AS cs
  ON ct.[schema_id] = cs.[schema_id]
WHERE rt.is_ms_shipped = 0 AND ct.is_ms_shipped = 0;

070 DropOrCreatePrimaryIndexes This one is actually a stored procedure. The rest are scripts

create Proc sp_DropOrCreatePrimaryIndexes
--  sp_DropOrCreatePrimaryIndexes 'create'
--  sp_DropOrCreatePrimaryIndexes 'drop'
 
@action CHAR(6) --drop or create
 
as
 
 
DECLARE @object_id int;
DECLARE @parent_object_id int;
DECLARE @TSQL NVARCHAR(4000);
DECLARE @COLUMN_NAME SYSNAME;
DECLARE @is_descending_key bit;
DECLARE @col1 BIT;
 
 
DECLARE PKcursor CURSOR FOR
    select kc.object_id, kc.parent_object_id
        from sys.key_constraints kc
            inner join sys.objects o on kc.parent_object_id = o.object_id
    where kc.type = 'PK' and o.type = 'U'
    and o.name not in ('dtproperties','sysdiagrams'-- not true user tables
    order by QUOTENAME(OBJECT_SCHEMA_NAME(kc.parent_object_id))
            ,QUOTENAME(OBJECT_NAME(kc.parent_object_id));
 
OPEN PKcursor;
FETCH NEXT FROM PKcursor INTO @object_id, @parent_object_id;
  
WHILE @@FETCH_STATUS = 0
BEGIN
    IF @action = 'DROP'
        SET @TSQL = 'ALTER TABLE '
                  + QUOTENAME(OBJECT_SCHEMA_NAME(@parent_object_id))
                  + '.' + QUOTENAME(OBJECT_NAME(@parent_object_id))
                  + ' DROP CONSTRAINT ' + QUOTENAME(OBJECT_NAME(@object_id))
    ELSE
        BEGIN
        SET @TSQL = 'ALTER TABLE '
                  + QUOTENAME(OBJECT_SCHEMA_NAME(@parent_object_id))
                  + '.' + QUOTENAME(OBJECT_NAME(@parent_object_id))
                  + ' ADD CONSTRAINT ' + QUOTENAME(OBJECT_NAME(@object_id))
                  + ' PRIMARY KEY'
                  + CASE INDEXPROPERTY(@parent_object_id
                                      ,OBJECT_NAME(@object_id),'IsClustered')
                        WHEN 1 THEN ' CLUSTERED'
                        ELSE ' NONCLUSTERED'
                    END
                  + ' (';
 
        DECLARE ColumnCursor CURSOR FOR
            select COL_NAME(@parent_object_id,ic.column_id), ic.is_descending_key
            from sys.indexes i
            inner join sys.index_columns ic
            on i.object_id = ic.object_id and i.index_id = ic.index_id
            where i.object_id = @parent_object_id
            and i.name = OBJECT_NAME(@object_id)
            order by ic.key_ordinal;
 
        OPEN ColumnCursor;
 
        SET @col1 = 1;
 
        FETCH NEXT FROM ColumnCursor INTO @COLUMN_NAME, @is_descending_key;
        WHILE @@FETCH_STATUS = 0
        BEGIN
            IF (@col1 = 1)
                SET @col1 = 0
            ELSE
                SET @TSQL = @TSQL + ',';
 
            SET @TSQL = @TSQL + QUOTENAME(@COLUMN_NAME)
                      + ' '
                      + CASE @is_descending_key
                            WHEN 0 THEN 'ASC'
                            ELSE 'DESC'
                        END;
 
            FETCH NEXT FROM ColumnCursor INTO @COLUMN_NAME, @is_descending_key;
        END;
 
        CLOSE ColumnCursor;
        DEALLOCATE ColumnCursor;
 
        SET @TSQL = @TSQL + ');';
 
        END;
 
    PRINT @TSQL;
 
    FETCH NEXT FROM PKcursor INTO @object_id, @parent_object_id;
END;
 
CLOSE PKcursor;
DEALLOCATE PKcursor;

090 Handle odd Constraint issue. When I got this error I manually created the scripts below by right clicking on the object and scripting it in SSMS.

/****** Object:  Index [UQ__RecoveryMaster__636F8578]    Script Date: 9/23/2017 4:20:49 PM ******/
ALTER TABLE [dbo].[RecoveryMaster] DROP CONSTRAINT [UQ__RecoveryMaster__636F8578]
ALTER TABLE [dbo].[RecoveryMaster] DROP CONSTRAINT [UQ__Recovery__72E12F1BC6DDD2CF]
 
 
ALTER TABLE [dbo].[RecoveryMaster] ADD UNIQUE NONCLUSTERED
(
    [name] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
 
ALTER TABLE [dbo].[RecoveryMaster] ADD UNIQUE NONCLUSTERED
(
    [name] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO

 

 

 

 

 

 

 

 

 

 

Please leave a comment

Add a Comment



Not Subscribed. You will not receive emails on article changes or comment additions

Comments

body header
Jason Burgess
Member since
04/01/19
I'm going to attempt this process for the first time. I'm doing it in a virtual (test) environment. As I was getting all the scripts together, I noticed that in the instructions, you mention to run the '080' script. However, I don't see that one listed like the rest of them. Can you please advise? Any help and tips for turning a Latin1_General_BIN collation to the standard collation would be appreciated!
roberthyndman
Linked In
Points: 3181
Has anyone used this technique successfully?  Does this still work for SQL 2016 and GP2018?
stevegray
4Penny.net
Points: 55509

Robert:

I wrote this a while ago, and haven't used it since. I just posted the code so that I'd have a starting point if I needed to do it again. Use with a great deal of care

roberthyndman
Linked In
Points: 3181
Thanks Steve.  Let me know how you go Jason.... I will probably get to do a test in a VM as well.  Cheers
Table Definition Quick Links
All Tables
SOP Tables
RM Tables
GL Tables
POP Tables
HR Tables
PM Tables
UPR Tables
IV Tables
Olympic Tables
3