Dado que este é um banco de dados existente que já possui tabelas definidas, há algumas implicações muito sérias na ação de alterar o agrupamento do banco de dados, além do impacto potencial de desempenho nas operações DML (que realmente já estavam lá). Há um impacto muito real no desempenho e na funcionalidade, e essa alteração não apenas não alcançou a meta pretendida (pelo menos não consistentemente), mas também provavelmente alterou o comportamento (ou alterará o comportamento quando novas tabelas forem criadas) em termos de como os dados são ordenados e equacionados.
Paul já forneceu boas explicações e exemplos das diferenças de desempenho e comportamento entre os diferentes tipos de agrupamentos em sua resposta, portanto não repetirei isso aqui. No entanto, alguns pontos precisam de alguns detalhes adicionais e existem vários outros pontos a serem adicionados em relação ao cenário atual de alteração do agrupamento de um banco de dados existente, em vez de definir o agrupamento de um novo banco de dados.
Os agrupamentos binários são mais do que apenas sensíveis a maiúsculas e minúsculas: são tudo sensíveis a maiúsculas ! Portanto, usando um agrupamento binário (terminando em _BIN
ou _BIN2
), suas comparações agora também são sensíveis ao sotaque, kana, largura e potencialmente sensíveis ao glúten (pelo menos essa parece ser a tendência nos dias de hoje ;-)). Esse foi o efeito desejado de fazer essa alteração? Os usuários finais esperam essa mudança de comportamento?
Os agrupamentos afetam não apenas as comparações, mas também a classificação. Um agrupamento binário será classificado com base no valor ASCII
ou UNICODE
byte (dependendo VARCHAR
ou NVARCHAR
, respectivamente) de cada byte . Portanto, ao escolher um agrupamento binário, você está renunciando a regras de ponderação específicas de idioma / cultura que ordenam cada caractere (mesmo caracteres em algum idioma, como o húngaro, composto de 2 letras) de acordo com o alfabeto dessa cultura. Portanto, se "ch" vier naturalmente depois de "k", bem, isso não acontecerá usando um agrupamento binário. Novamente, esse foi o efeito desejado de fazer essa alteração? Os usuários finais esperam essa mudança de comportamento?
A menos que você tenha requisitos específicos de compatibilidade com versões anteriores para seu aplicativo, você deve usar os agrupamentos em BIN2
vez de BIN
, assumindo, é claro, que deseja um agrupamento binário em primeiro lugar. Os BIN2
agrupamentos foram introduzidos no SQL Server 2005 e de acordo com a página do MSDN das Diretrizes para o uso de agrupamentos BIN e BIN2 :
Os agrupamentos binários anteriores no SQL Server, aqueles que terminam com "_BIN", executaram uma comparação incompleta de código a ponto a ponto para dados Unicode. Os agrupamentos binários mais antigos do SQL Server compararam o primeiro caractere como WCHAR, seguido por uma comparação de byte a byte.
...
Você pode migrar para os agrupamentos binários [_BIN2] para aproveitar as comparações verdadeiras de pontos de código e deve usar os novos agrupamentos binários para o desenvolvimento de novos aplicativos.
Também deve-se observar que os _BIN2
agrupamentos correspondem convenientemente ao comportamento da Ordinal
opção da StringComparison Enumeration , de modo que comparações e classificações feitas no código .NET usando essa opção produzam os mesmos resultados que as mesmas operações executadas no SQL Server (ao usar os _BIN2
agrupamentos, é claro).
Por razões semelhantes às que acabamos de dizer sobre os _BIN2
agrupamentos, a menos que você tenha requisitos específicos para manter o comportamento de compatibilidade com versões anteriores, inclua-se no uso dos agrupamentos do Windows e não dos agrupamentos específicos do SQL Server (ou seja, os que começam com SQL_
agora são considerados meio "chato" ;-)).
Ao usar dados Unicode (por exemplo, sequência de caracteres prefixada N
ou inserida no SQL Server a partir do código do aplicativo em que o tipo de dados foi especificado como NChar
ou NVarChar
), não vejo como o uso de um agrupamento versus outro faria diferença na inserção ou atualização de um campo NCHAR
ou NVARCHAR
string .
Ao usar dados não Unicode, inserir ou atualizar um campo não Unicode, o agrupamento específico (banco de dados ou campo) pode desempenhar um pequeno papel se qualquer caractere inserido / atualizado precisar ser traduzido ou não for mapeado (é mesmo uma palavra?), conforme especificado pela Página de Código definida pelo agrupamento. Obviamente, esse problema em potencial existe sempre que se usa dados ou tipos de dados não Unicode e não é específico para esse cenário de alteração do agrupamento de banco de dados. Essa alteração afetará os literais das strings (o que pode já ter sido um problema se o agrupamento do banco de dados fosse diferente do agrupamento do campo). Porém, mesmo que nenhuma alteração seja feita no agrupamento do banco de dados, os dados provenientes de outros bancos de dados ou de fora do SQL Server (qualquer código de cliente) podem conter qualquer caractere e ter uma codificação específica.
MUITO IMPORTANTE!!! Ao alterar o agrupamento padrão do banco de dados, o agrupamento especificado para qualquer campo de sequência existente em qualquer tabela existente não será alterado, mas quaisquer novos campos terão um agrupamento do padrão do banco de dados (a menos que seja substituído pela COLLATE
cláusula). Isso afetará suas consultas de três maneiras:
1) Se alguma consulta Juntar-se a algum desses campos existentes a qualquer um dos novos campos, você receberá um erro de incompatibilidade de agrupamento:
USE [master];
GO
IF (DB_ID(N'ChangeCollationTest') IS NOT NULL)
BEGIN
PRINT 'Dropping [ChangeCollationTest] DB...';
ALTER DATABASE [ChangeCollationTest]
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE [ChangeCollationTest];
END;
GO
PRINT 'Creating [ChangeCollationTest] DB...';
CREATE DATABASE [ChangeCollationTest]
COLLATE SQL_Latin1_General_CP1_CI_AS;
GO
USE [ChangeCollationTest];
GO
CREATE TABLE [CollateTest-SQL_Latin1_General_CP1_CI_AS]
(Col1 NVARCHAR(50) COLLATE DATABASE_DEFAULT, Col2 NVARCHAR(50));
SELECT *
FROM sys.columns sc
WHERE sc.[object_id] = OBJECT_ID(N'[CollateTest-SQL_Latin1_General_CP1_CI_AS]');
-- "collation_name" for both fields shows: SQL_Latin1_General_CP1_CI_AS
GO
USE [master];
GO
ALTER DATABASE [ChangeCollationTest]
COLLATE Latin1_General_BIN2;
GO
USE [ChangeCollationTest];
GO
CREATE TABLE [CollateTest-Latin1_General_BIN2]
(Col1 NVARCHAR(50) COLLATE DATABASE_DEFAULT, Col2 NVARCHAR(50));
SELECT *
FROM sys.columns sc
WHERE sc.[object_id] = OBJECT_ID(N'[CollateTest-Latin1_General_BIN2]');
-- "collation_name" for both fields shows: Latin1_General_BIN2
GO
SELECT *
FROM dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] ctSQL
INNER JOIN dbo.[CollateTest-Latin1_General_BIN2] ctWIN
ON ctWIN.Col1 = ctSQL.Col1;
Devoluções:
Msg 468, Level 16, State 9, Line 4
Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and
"Latin1_General_BIN2" in the equal to operation.
2) Predicados / filtros em campos existentes de tabelas existentes (configurados com o agrupamento padrão anterior) que são comparados a literais ou variáveis de cadeia não causarão erro, mas certamente podem ter impacto no desempenho devido ao SQL Server precisar equiparar o agrupamento de ambos os lados e convertendo automaticamente a string literal ou variável para o agrupamento do campo. Ative "Incluir plano de execução real" (Control-M) e execute o seguinte (assumindo que você já executou as consultas mostradas acima):
SELECT *
FROM dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] ctSQL
WHERE ctSQL.Col1 = N'a';
-- Unspecified collations on string literals and variables assume the database default
-- collation. This mismatch doesn't cause an error because SQL Server adds a
-- "[Col1]=CONVERT_IMPLICIT(nvarchar(4000),[@1],0)" but it can hurt performance.
SELECT *
FROM dbo.[CollateTest-Latin1_General_BIN2] ctWIN
WHERE ctWIN.Col1 = N'a';
-- No CONVERT_IMPLICIT; plan shows "[Col1]=[@1]".
3) E, falando em conversões implícitas, observe como é a string literal (com um agrupamento implícito do agrupamento padrão do banco de dados:) Latin1_General_BIN2
que é convertida, não o campo na tabela. Alguma sugestão sobre se esse filtro não diferencia maiúsculas de minúsculas (o agrupamento antigo) ou faz distinção entre maiúsculas e minúsculas (o novo agrupamento)? Execute o seguinte para ver:
INSERT INTO dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] (Col1)
VALUES (N'a'), (N'A');
SELECT ctSQL.Col1
FROM dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] ctSQL
WHERE ctSQL.Col1 = N'a';
Devoluções:
Col1
----
a
A
D'oh! Não apenas existe um desempenho leve (ou talvez mais significativo?) Para essa consulta devido ao CONVERT_IMPLICIT()
, mas ele nem se comporta da maneira que diferencia maiúsculas de minúsculas.
Portanto, se o agrupamento for alterado em um banco de dados que já possui tabelas, sim, o desempenho e a funcionalidade serão afetados.
Se o agrupamento estiver sendo definido em um novo banco de dados, Paulo já o abordou, explicando como um agrupamento binário, embora rápido, provavelmente não será da maneira que alguém esperaria ou desejaria.
Observe também que você sempre pode especificar agrupamentos por condição. A cláusula COLLATE pode ser adicionada às WHERE
condições ORDER BY
, e a qualquer lugar que aceite uma sequência.
Exemplo 1 (condição WHERE):
SELECT tmp.col AS [SQL-CaseSensitive]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
WHERE tmp.col > 'a' COLLATE SQL_Latin1_General_CP1_CS_AS;
SELECT tmp.col AS [Windows-CaseSensitive]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
WHERE tmp.col > 'a' COLLATE Latin1_General_CS_AI;
Devoluções:
SQL-CaseSensitive
-----------------
b
B
Windows-CaseSensitive
-----------------
A
b
B
Exemplo 2 (PEDIDO POR):
SELECT tmp.col AS [Windows-CaseSensitive]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
ORDER BY tmp.col COLLATE Latin1_General_CS_AI;
SELECT tmp.col AS [Windows-Binary]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
ORDER BY tmp.col COLLATE Latin1_General_BIN2;
Devoluções:
Windows-CaseSensitive
-----------------
a
A
b
B
Windows-Binary
-----------------
A
B
a
b
Exemplo 3 (instrução IF):
IF ('A' = 'a') SELECT 1 AS [DatabaseDefault-CaseInsensitive?];
-- if the DB is not case-sensitive or binary, returns 1
IF ('A' = 'a' COLLATE Latin1_General_BIN2) SELECT 2 AS [Windows-Binary];
Devoluções:
DatabaseDefault-CaseInsensitive?
--------------------------------
1
{nothing}
Exemplo 4 (associado ao parâmetro de entrada da função):
SELECT UNICODE(N'🂡') AS [UCS-2],
UNICODE(N'🂡' COLLATE Latin1_General_100_CI_AS_SC) AS [UTF-16];
-- This character is a Unicode supplemental character and is not part of the
-- default UCS-2 encoding. In order for built-in functions to handle these
-- characters correctly, either the DB default collation needs to end in
-- "_SC" (available as of SQL Server 2012), or use as shown here.
-- See the character in more detail here: http://unicode-table.com/en/1F0A1/
Devoluções:
UCS-2 UTF-16
------ -------
55356 127137
O valor UCS-2 de 55.356 está parcialmente correto, pois é o primeiro dos dois valores no "par substituto". Mas, a menos que seja explicitamente fornecido o _SC
agrupamento, a UNICODE()
função pode ver apenas cada caractere como um valor de byte duplo e não sabe como lidar adequadamente com um par substituto de byte duplo.
ATUALIZAR
Mesmo com todos os exemplos acima, um aspecto das comparações entre maiúsculas e minúsculas que geralmente é ignorado e negado por comparações / agrupamentos binários é a normalização (composição e decomposição) que faz parte do Unicode.
Exemplo 5 (quando uma comparação binária não é diferencia maiúsculas de minúsculas):
As comparações que diferenciam maiúsculas de minúsculas permitem combinar caracteres que, em combinação com outro caractere, formam outro caractere que já existe como outro ponto de código Unicode. As comparações que diferenciam maiúsculas de minúsculas se preocupam com o caractere exibível, não com os pontos de código usados para criá-lo.
SELECT 'Equal' AS [Binary],
NCHAR(0x00FC) AS [ü],
N'u' + NCHAR(0x0308) AS [u + combining diaeresis]
WHERE NCHAR(0x00FC) COLLATE Latin1_General_100_BIN2
= N'u' + NCHAR(0x0308) COLLATE Latin1_General_100_BIN2
-- No result as they are a different number of code points,
-- as well as being different code points.
SELECT 'Equal' AS [Case-Sensitive],
NCHAR(0x00FC) AS [ü],
N'u' + NCHAR(0x0308) AS [u + combining diaeresis]
WHERE NCHAR(0x00FC) COLLATE Latin1_General_100_CS_AS -- ü
= N'u' + NCHAR(0x0308) COLLATE Latin1_General_100_CS_AS -- u + combining diaeresis
-- Result set returned, even being a different number of code points AND Accent Sensitive,
-- due to normalization
Devoluções:
Binary ü u + combining diaeresis
------- --- -------------------------
{nothing}
Case-Sensitive ü u + combining diaeresis
--------------- --- -------------------------
Equal ü ü
As verdadeiras comparações que diferenciam maiúsculas de minúsculas também permitem que caracteres largos sejam iguais aos seus equivalentes não amplos.
IF (N'sofia' = N'sofia' COLLATE Latin1_General_100_BIN2)
SELECT 'Values are the same' AS [Binary]
ELSE
SELECT 'Values are different' AS [Binary];
IF (N'sofia' = N'sofia' COLLATE Latin1_General_100_CS_AS)
SELECT 'Values are the same' AS [Case-Sensitive]
ELSE
SELECT 'Values are different' AS [Case-Sensitive];
Devoluções:
Binary
---------------
Values are different
Case-Sensitive
---------------
Values are the same
Ergo:
Os agrupamentos BINÁRIOS ( _BIN
e _BIN2
) não diferenciam maiúsculas de minúsculas!