What is collation and how it changes in SQL Server

With one sentence collation is the way characters are encoded in a database. That is, you define whether it will be case sensitive (uppercase, lowercase), what language the varchar and text fields are in.
Fields like nvarchar which are in Unicode format are not affected by the encoding of the characters, but it affects the sort since depending on the collation there may be a different order of letters or be affected by the case sensitive.
The collation on system databases and on user databases could be different from each other since SQL Server 2000 and later.
How can we see what collation all databases have with query
select name,databasepropertyex(name,'COLLATION'),databasepropertyex(name,'Recovery'),databasepropertyex(name,'STATUS') from master.dbo.sysdatabases

The collation at master database defines the collation in the rest of the system bases as well. This may affect system functions such as database login and sort on tempdb.
The collation on user databases affects as we mentioned first of all what language the non-unicode characters and there is a basic problem when we decide to change it. That whatever data was passed up to the moment of the change will remain with the collation they had.
Even if we have the wrong collation in one base, we can read the field with a different collation, e.g. on a base that is in SQL_Latin1_General_CP1253_CI_AI with the following query we can read the Greek characters:
select cast (Onoma as varchar(100)) collate Greek_CI_AS from Pelatis
To change the collation in the database
To change the collation to user databases the following steps should be taken so that we do not have the problem we mentioned above and that not all records in the database have the same collation (it is good to have done them backup before we start):
1 – We export the generate script of the base (its structure):
Right click on the database, Tasks, Generate scripts… , Script entire database and all database objects
2 – With the following script that I have made, generate the statements so that we can do bcp out and bcp in the whole base. We execute it and keep both columns.
declare @user varchar(50), @pass varchar(50), @pathout varchar(200), @pathin varchar(200) set @user = 'username' set @pass = 'password' set @pathout = 'c:\backups\' set @pathin = 'c:\backups\' SELECT 'bcp ' --bcp + QUOTENAME(DB_NAME())+ '.' + QUOTENAME(SCHEMA_NAME(SCHEMA_ID))+ '.' -- + QUOTENAME(name) + ' out ' +@pathout + REPLACE(SCHEMA_NAME(schema_id),' ','') + '_' + REPLACE(name,' ','') + '.txt -S' + REPLACE(REPLACE(QUOTENAME(@@servername),'[',''),']','') --server name +' -U'+@user+' -P'+@pass+' ' --username pass +'-n -t[cdel] -r[rdel]' , AS BCP_OUT, 'bcp ' --bcp + QUOTENAME(DB_NAME())+ '.' + QUOTENAME(SCHEMA_NAME(SCHEMA_ID))+ '.' + QUOTENAME(name) + ' in '+@pathin + REPLACE(SCHEMA_NAME(schema_id),' ','') + '_' + REPLACE(name,' ','') + '.txt -S' + REPLACE(REPLACE(QUOTENAME(@@servername),'[',''),']','') --server name +' -U'+@user+' -P'+@pass+' ' --username pass +'-w -n' +' -e'+@pathin +'logbcpin.txt' +' -t[cdel] -r[rdel]' , AS BCP_IN from sys.tables
3 – We execute it bcp out in command prompt from step 2.
4 – Turn off the base.
drop database [AdventureWorks2017 ]
5 – We run the script that creates the database from step 1.
6 – We change the collation in the base.
ALTER DATABASE AdventureWorks2017 GREEK_CI_AI ;GO
7 – We execute it bcp in in command prompt from step 2. (after first disabling the constraints)
EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"
8 – When the bcp in is completed, enable the constraints.
EXEC sp_msforeachtable "ALTER TABLE ? CHECK CONSTRAINT all"
To change the collation on the system bases
To change the collation in the system bases it is necessary rebuild the master and this results in:
- May all be lost jobs from msdb
- May all be lost logins kai oi clock
- Let them be lost dblinks
- To lose all the rights that have been passed to the roles such as e.g. to the public
- To be done detach All the user databases
- Let them all perish the parameters of the instance (which we had in sp_configure) such as max_memory, max_dop etc.
The procedure for changing the collation in systemic ones is as follows:
1 – Backup of the master, msdb, model databases (with the corresponding modification of the paths in the following script that I have made)
DECLARE @date CHAR(8)
SET @date = (SELECT CONVERT(char(8), GETDATE(), 112))
DECLARE @backup_loc VARCHAR(125)
SET @backup_loc = 'X:\backups\' -- Σε ποιά τοποθεσία θα παρθούν τα backups
DECLARE @rest_dbf VARCHAR(125)
SET @rest_dbf = 'D:\Databases\' -- Σε ποιά τοποθεσία θα γίνουν restore τα datafiles
DECLARE @rest_log VARCHAR(125)
SET @rest_log = 'L:\Logfiles\'; -- Σε ποια τοποθεσία θα γίνουν restore τα logfiles
WITH CTE ( DatabaseName, Npath )
AS ( SELECT DISTINCT
DB_NAME(database_id) ,
STUFF((SELECT ' ' + CHAR(13)+', MOVE ''' + name + ''''
+ CASE Type
WHEN 0 THEN ' TO ' +@rest_dbf
ELSE ' TO ' +@rest_log
END
+ REVERSE(LEFT(REVERSE(physical_name),
CHARINDEX('\',
REVERSE(physical_name),
1) - 1)) + ''''
FROM sys.master_files sm1
WHERE sm1.database_id = sm2.database_ID
FOR XML PATH('') ,
TYPE).value('.', 'varchar(max)'), 1, 1, '') AS Npath
FROM sys.master_files sm2
)
--select * from CTE
SELECT
'BACKUP DATABASE ' + name + ' TO DISK = ''' + @backup_loc + '' + name + '_' + @date + '.bak'' WITH COMPRESSION, COPY_ONLY, STATS=5' as Backup_Commands_onSource,
'RESTORE DATABASE '+ name + ' FROM DISK = ''' + @backup_loc + '' + name + '_' + @date + '.bak'' WITH RECOVERY, REPLACE, STATS=5 ' + CTE.Npath as Restore_Commands_onTarget
FROM sys.databases d
INNER JOIN CTE ON d.name = cte.databasename
WHERE d.name in('master','model','msdb') --DBs
GO
2 – Backup of jobs (press F7 in SSMS to open the object explorer details, select all jobs and right click, script job as, create to, file.. as in the image below):

3 – Backup logins, users, roles with Greg Ryan's query:
--Script All Logins / Users / and Roles
/****************************************************************
This Script Generates A script to Create all Logins, Server Roles
, DB Users and DB roles on a SQL Server
Greg Ryan
10/31/2013
****************************************************************/
SET NOCOUNT ON
DECLARE
@sql nvarchar(max)
, @Line int = 1
, @max int = 0
, @@CurDB nvarchar(100) = ''
CREATE TABLE #SQL
(
Idx int IDENTITY
,xSQL nvarchar(max)
)
INSERT INTO #SQL
( xSQL
)
SELECT
'IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = N'''
+ QUOTENAME(name) + ''')
' + ' CREATE LOGIN ' + QUOTENAME(name) + ' WITH PASSWORD='
+ sys.fn_varbintohexstr(password_hash) + ' HASHED, SID='
+ sys.fn_varbintohexstr(sid) + ', ' + 'DEFAULT_DATABASE='
+ QUOTENAME(COALESCE(default_database_name , 'master'))
+ ', DEFAULT_LANGUAGE='
+ QUOTENAME(COALESCE(default_language_name , 'us_english'))
+ ', CHECK_EXPIRATION=' + CASE is_expiration_checked
WHEN 1 THEN 'ON'
ELSE 'OFF'
END + ', CHECK_POLICY='
+ CASE is_policy_checked
WHEN 1 THEN 'ON'
ELSE 'OFF'
END + '
Go
'
FROM
sys.sql_logins
WHERE
name <> 'sa'
INSERT INTO #SQL
( xSQL
)
SELECT
'IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = N'''
+ QUOTENAME(name) + ''')
' + ' CREATE LOGIN ' + QUOTENAME(name) + ' FROM WINDOWS WITH '
+ 'DEFAULT_DATABASE='
+ QUOTENAME(COALESCE(default_database_name , 'master'))
+ ', DEFAULT_LANGUAGE='
+ QUOTENAME(COALESCE(default_language_name , 'us_english'))
+ ';
Go
'
FROM
sys.server_principals
WHERE
type IN ( 'U' , 'G' )
AND name NOT IN ( 'BUILTIN\Administrators' ,
'NT AUTHORITY\SYSTEM' );
PRINT '/*****************************************************************************************/'
PRINT '/*************************************** Create Logins ***********************************/'
PRINT '/*****************************************************************************************/'
SELECT
@Max = MAX(idx)
FROM
#SQL
WHILE @Line <= @max
BEGIN
SELECT
@sql = xSql
FROM
#SQL AS s
WHERE
idx = @Line
PRINT @sql
SET @line = @line + 1
END
DROP TABLE #SQL
CREATE TABLE #SQL2
(
Idx int IDENTITY
,xSQL nvarchar(max)
)
INSERT INTO #SQL2
( xSQL
)
SELECT
'EXEC sp_addsrvrolemember ' + QUOTENAME(L.name) + ', '
+ QUOTENAME(R.name) + ';
GO
'
FROM
sys.server_principals L
JOIN sys.server_role_members RM
ON L.principal_id = RM.member_principal_id
JOIN sys.server_principals R
ON RM.role_principal_id = R.principal_id
WHERE
L.type IN ( 'U' , 'G' , 'S' )
AND L.name NOT IN ( 'BUILTIN\Administrators' ,
'NT AUTHORITY\SYSTEM' , 'sa' );
PRINT '/*****************************************************************************************/'
PRINT '/******************************Add Server Role Members *******************************/'
PRINT '/*****************************************************************************************/'
SELECT
@Max = MAX(idx)
FROM
#SQL2
SET @line = 1
WHILE @Line <= @max
BEGIN
SELECT
@sql = xSql
FROM
#SQL2 AS s
WHERE
idx = @Line
PRINT @sql
SET @line = @line + 1
END
DROP TABLE #SQL2
PRINT '/*****************************************************************************************/'
PRINT '/*****************Add User and Roles membership to Indivdual Databases********************/'
PRINT '/*****************************************************************************************/'
--Drop Table #Db
CREATE TABLE #Db
(
idx int IDENTITY
,DBName nvarchar(100)
);
INSERT INTO #Db
SELECT
name
FROM
master.dbo.sysdatabases
WHERE
name NOT IN ( 'Master' , 'Model' , 'msdb' , 'tempdb' )
ORDER BY
name;
SELECT
@Max = MAX(idx)
FROM
#Db
SET @line = 1
--Select * from #Db
--Exec sp_executesql @SQL
WHILE @line <= @Max
BEGIN
SELECT
@@CurDB = DBName
FROM
#Db
WHERE
idx = @line
SET @SQL = 'Use ' + @@CurDB + '
Declare @@Script NVarChar(4000) = ''''
DECLARE cur CURSOR FOR
Select ''Use ' + @@CurDB + ';
Go
IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = N'''''' +
mp.[name] + '''''')
CREATE USER ['' + mp.[name] + ''] FOR LOGIN ['' +mp.[name] + ''] WITH DEFAULT_SCHEMA=[dbo]; ''+ CHAR(13)+CHAR(10) +
''GO'' + CHAR(13)+CHAR(10) +
''EXEC sp_addrolemember N'''''' + rp.name + '''''', N''''['' + mp.[name] + '']'''';
Go''
FROM sys.database_role_members a
INNER JOIN sys.database_principals rp ON rp.principal_id = a.role_principal_id
INNER JOIN sys.database_principals AS mp ON mp.principal_id = a.member_principal_id
OPEN cur
FETCH NEXT FROM cur INTO @@Script;
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT @@Script
FETCH NEXT FROM cur INTO @@Script;
END
CLOSE cur;
DEALLOCATE cur;';
--Print @SQL
Exec sp_executesql @SQL;
--Set @@Script = ''
SET @Line = @Line + 1
END
DROP TABLE #Db
4 – Backup db links, certificates (If they exist). It can be easily done with the SSMS GUI.

5 – We do detach bases with the following script we can automate it:
USE [master];
GO
DECLARE @database NVARCHAR(200) ,
@cmd NVARCHAR(1000) ,
@detach_cmd NVARCHAR(4000) ,
@attach_cmd NVARCHAR(4000) ,
@file NVARCHAR(1000) ,
@i INT ,
@whichdb INT=0,
@dbname nvarchar(200)='%%',
@Drive_Now nvarchar(10)='',
@Drive_New nvarchar(10)='';
create table #tempattach(attach_script varchar(max));
create table #tempdetach(detach_script varchar(max));
--set 4 for only userdbs, 0 for all dbs
set @whichdb = 4
--if u want to attach db on new drive with same path uncomment with the correct drive letters
--set @Drive_Now = 'C:\' set @Drive_New = 'Z:\'
--uncomment if u pick only one DBname
--set @dbname = '%iDBA%'
DECLARE dbname_cur CURSOR STATIC LOCAL FORWARD_ONLY
FOR
SELECT RTRIM(LTRIM([name]))
FROM sys.databases
WHERE database_id > @whichdb
and name like @dbname;
OPEN dbname_cur
FETCH NEXT FROM dbname_cur INTO @database
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @i = 1;
SET @attach_cmd =
'EXEC sp_attach_db @dbname = ''' + @database + '''' + CHAR(10);
-- Change skip checks to false if you want to update statistics before you detach.
SET @detach_cmd =
'EXEC sp_detach_db @dbname = ''' + @database
+ ''' , @skipchecks = ''true'';' + CHAR(10);
-- Get a list of files for the database
DECLARE dbfiles_cur CURSOR STATIC LOCAL FORWARD_ONLY
FOR
SELECT physical_name
FROM sys.master_files
WHERE database_id = DB_ID(@database)
ORDER BY [file_id];
OPEN dbfiles_cur
FETCH NEXT FROM dbfiles_cur INTO @file
WHILE @@FETCH_STATUS = 0
BEGIN
SET @attach_cmd = @attach_cmd + ' ,@filename'
+ CAST(@i AS NVARCHAR(10)) + ' = ''' + @file + ''''
+ CHAR(10);
SET @i = @i + 1;
FETCH NEXT FROM dbfiles_cur INTO @file
END
CLOSE dbfiles_cur;
DEALLOCATE dbfiles_cur;
insert into #tempattach values(REPLACE(@attach_cmd,@Drive_Now,@Drive_New));
insert into #tempdetach values(@detach_cmd);
FETCH NEXT FROM dbname_cur INTO @database
END
CLOSE dbname_cur;
DEALLOCATE dbname_cur;
select * from #tempdetach
select * from #tempattach
drop table #tempattach
drop table #tempdetach
6 – Double click on the SQL Server ISO whose instance it is, e.g. 2016
7 – In the command prompt as an administrator, cd to the drive where the ISO was mounted and run the following command, after first putting the correct instance name, windows authentication, and the collation we want to go to:
F:\>cd F: Setup /QUIET /ACTION=REBUILDDATABASE /INSTANCENAME=MSSQLSERVER /SQLSYSADMINACCOUNTS=INSTANCENAME\SA /SAPWD=KWDIKOS /SQLCOLLATION=SQL_Latin1_General_CP1253_CI_AI
8 – The change is complete, so we have to go through the jobs, logins, parameters, etc. again.
9 – Attach the user databases. (from the results of step 5)


There is an error. No commas are needed in column names (,AS)
The queries have been tested. They work normally.
We must be more careful in what we recommend because we may be led into irreversible mistakes. The database structure as a whole is not done with what you suggest (SCRIPT DATABASE AS—>File). This makes the base's characteristics script and not its objects (Tables etc). The result is that when someone goes to pass the data, the corresponding tables do not exist!!! So if someone hadn't backed up the entire database then they will have to rebuild its structure from scratch by hand!!!
By mistake I had mentioned the Script Database option instead of Generate Scripts which exports the entire database schema along with the schema of all tables so the steps mentioned apply normally.