web-dev-qa-db-fra.com

SQL Server: la modification du classement sur les bases de données existantes est-elle sûre?

Je suis nouveau sur SQL Server et je n'étais pas conscient de la possibilité que T-SQL soit insensible à la casse.

J'ai normalisé des entités en séparant des tables et des contraintes. J'ai peur que si je modifie le classement maintenant, que certaines de ces contraintes puissent être invalides si, par exemple, une valeur de clé étrangère "insensible à la casse" pointe vers une valeur PK avec une casse différente.

Si je devais exécuter cette requête sur la base de données (testée sur une base de données de base):

ALTER DATABASE <db_name>
COLLATE SQL_Latin1_General_Cp1_CS_AS ;
GO

Que se passerait-il si certaines contraintes étaient violées?

Y a-t-il une autre raison pour laquelle je devrais être prudent en effectuant un changement à l'échelle de la base de données, comme changer le classement?

7
Zach Smith

La modification du classement d'une base de données n'affecte pas les colonnes existantes. Il affecte les nouvelles colonnes de chaîne non XML créées qui ne spécifient pas la clause COLLATE (y compris les variables de table), les littéraux de chaîne et les valeurs de variable (pas la résolution de nom de variable, qui est déterminée par le classement au niveau de l'instance) . Cela signifie que quelque chose comme ce qui suit sera affecté:

IF (@Variable = 'string')
BEGIN
    ...
END;

Cette modification affectera également les métadonnées au niveau de la base de données, telles que les noms des schémas, des objets, des colonnes, des index, etc. Cela signifie que les deux scénarios suivants seront affectés:

SELECT ...
FROM   sys.indexes si
WHERE  si.[name] = N'somename'; -- real name = SomeName

et:

SELECT ...
FROM   dbo.sometable st -- real name = SomeTable

Dans ces deux exemples, ils fonctionneraient dans un classement insensible à la casse, mais ne renverraient rien ou erreur, respectivement, dans un classement sensible à la casse.

Enfin, comme @JonathanFite a eu la gentillesse de me le rappeler, changer le classement DB - mai impacte les requêtes impliquant des tables temporaires. Le classement par défaut des colonnes de chaînes dans les tables temporaires (pas les variables de table) est le classement par défaut pour [tempdb] (qui doit être identique à [model], qui devrait être la valeur par défaut de l'instance, sauf si quelqu'un a restauré [model] depuis un serveur qui avait un classement par défaut différent), pas le classement de la base de données locale. Cela signifie que même si les tables temporaires sont créées à chaque fois et que vous pouvez donc vous attendre à ce qu'elles agissent comme des "tables nouvellement créées" avec le nouveau classement, elles agiront en fait comme des "tables existantes" et continueront de se comporter comme elles le faisaient avant le changement de classement. Si vous avez besoin de colonnes de chaînes dans des tables temporaires pour utiliser le nouveau classement, vous devrez définir explicitement leur classement à l'aide de COLLATE DATABASE_DEFAULT dans le CREATE TABLE déclarations.

Par conséquent, vous devez vraiment faire beaucoup de tests!

Si vous souhaitez modifier les colonnes existantes, vous devrez supprimer les contraintes existantes, émettre un ALTER TABLE ... ALTER COLUMN, puis recréez les contraintes. Vous devrez également reconstruire les index qui utilisent n'importe quelle colonne dont le classement a été modifié car l'ordre de tri peut être différent.

En outre, il est préférable de ne pas utiliser les classements commençant par SQL_. Utilisez plutôt Latin1_General_100_CS_AS. Les classements commençant par SQL_ sont obsolètes (même s'ils ne sont pas officiellement obsolètes) depuis la sortie de SQL Server 2000 . Leur gestion des données VARCHAR/8 bits est obsolète et n'est pas conforme à un comportement plus récent. Malheureusement, pour des raisons de compatibilité descendante, le classement par défaut pour les installations en anglais américain était le SQL_Latin1 Classements, comme indiqué dans la page tilisation des classements SQL Server MSDN:

Pour des raisons de compatibilité ascendante, le classement par défaut en anglais (US) est SQL_Latin1_General *.

Ceci est également noté dans le tableau de classement par défaut de la page Paramètres de classement dans le programme d'installation MSDN (appuyez sur Control-F et collez-le dans sql_latin). Je crois que cette valeur par défaut a été remplacée par un classement Windows à partir de SQL Server 2014, mais la documentation, même pour la configuration de SQL Server 2016, pointe toujours vers la page de configuration 2008 R2 pour les classements.


Voici un script pour voir certaines des différences de comportement lors de la modification du classement d'une base de données:

USE [master];
GO

IF (DB_ID(N'ChangeDatabaseCollationTest') IS NULL)
BEGIN
    CREATE DATABASE [ChangeDatabaseCollationTest] COLLATE Latin1_General_100_CI_AS;
END;
GO

USE [ChangeDatabaseCollationTest];
GO
-- Current DB Collation: Latin1_General_100_CI_AS

EXEC sp_help 'sys.objects';
-- Collation for [name] = Latin1_General_100_CI_AS

IF ('A' = 'a')
BEGIN
    SELECT 'Case INsensitive comparison works.';
END;
ELSE
BEGIN
    SELECT 'Case INsensitive comparison did NOT work.';
END;
-- Case INsensitive comparison works.


CREATE TABLE dbo.CaseTest_a (ID INT); -- success
SELECT * FROM dbo.CaseTest_A; -- success


CREATE TABLE dbo.CaseTest_A (ID INT); -- error:
-- Msg 2714, Level 16, State 6, Line 5
-- There is already an object named 'CaseTest_A' in the database.




ALTER DATABASE [ChangeCollationTest] COLLATE Latin1_General_100_CS_AS; -- success

IF ('A' = 'a')
BEGIN
    SELECT 'Case INsensitive comparison works.';
END;
ELSE
BEGIN
    SELECT 'Case INsensitive comparison did NOT work.';
END;
-- Case INsensitive comparison did NOT work.


SELECT * FROM dbo.CaseTest_A; -- error:
-- Msg 208, Level 16, State 1, Line 56
-- Invalid object name 'dbo.CaseTest_A'.


CREATE TABLE dbo.CaseTest_A (ID INT); -- success

EXEC sp_help 'sys.objects';
-- Collation for [name] = Latin1_General_100_CS_AS


ALTER DATABASE [ChangeCollationTest] COLLATE Latin1_General_100_CI_AS; -- error:
-- Msg 1505, Level 16, State 1, Line 23
-- The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for
--    the object name 'dbo.sysschobjs' and the index name 'nc1'. The duplicate key
--    value is (0, 1, CaseTest_A).
-- Msg 5072, Level 16, State 1, Line 23
-- ALTER DATABASE failed. The default collation of database 'ChangeCollationTest'
--    cannot be set to Latin1_General_100_CI_AS.
6
Solomon Rutzky

Rien ne se passera .. initialement. Le classement de la base de données n'est qu'une valeur par défaut qui est copiée dans les nouvelles colonnes lors de leur création. Une fois créés, ils conservent ce classement. Voir sys.columns.collation_name .

Si vous modifiez le classement de la base de données, puis créez de nouvelles colonnes, ces colonnes ont le nouveau classement. Cela peut être compatible ou non avec les colonnes préexistantes.

Si vous adoptez un classement sensible à la casse pour la base de données, SQL devient sensible à la casse. Les noms d'objets devront correspondre exactement aux chaînes déclarées. Cela peut casser l'application.

J'ai suivi ce processus il y a de nombreuses années. Je semble me souvenir que j'ai dû ALTER explicitement chaque colonne de caractères de la base de données vers le nouveau classement, puis mettre à jour les données pour que le changement de classement démarre (mise à jour du jeu de tables charcol1 = charcol1, charcol2 = charcol2 ...; non-string les colonnes n'étaient pas nécessaires). C'était il y a plusieurs versions, donc les choses peuvent être plus faciles maintenant. Si je devais le faire à nouveau, selon la taille et la complexité, je serais tenté de scripter tous les objets, de modifier le fichier pour supprimer les classements, puis de créer une nouvelle base de données à partir de zéro et de transférer les données et les autorisations, etc.

Bonne chance.

2
Michael Green