Collation e encoding em bancos de dados

2024-08-29 · 14 min read

Read in english

Introdução

Toda informação em um computador é armazenada e processada na sua representação em número binário.

Mas e os textos? Um texto é uma seqüência de caractéres, um caractér sendo uma letra ou um símbolo. Cada caractér é representado por um número binário, sendo que o mapeamento caractér-número é chamado de encoding.

A tabela abaixo é do encoding Windows-1252, que contempla 255 caractéres possíveis, cada um representado por um byte. Você pode ver essa tabela com o Mapa de Caractéres do Windows (Win+R, charmap).

Tabela do encoding Windows-1252

A collation, por sua vez, é a regra de ordenação usada para comparar textos. Ela também determina, junto consigo, qual é o encoding utilizado.

No Microsoft SQL Server, no nome de uma collation, CI / CS significa case (in)sensitive - diferenciação por maiúsculas e minúsculas; AI / AS, accent (in)sensitive - diferenciação por acentos.

Com uma collation case-insensitive (CI), por exemplo, tanto faz pesquisar João da Silva ou joão da silva.

SELECT
  [Name],
  COLLATIONPROPERTY( [Name], 'LCID' ) AS [LCID],
  COLLATIONPROPERTY( [Name], 'CodePage' ) AS [CodePage]
FROM sys.fn_helpcollations()
ORDER BY [Name];
NomeEncodingCompara casingCompara acentos
Latin1_General_
CI_AS
Windows-1252NãoSim
Latin1_General_
100_CI_AS_
KS_SC_UTF8
UTF-8 (65001)NãoSim
Latin1_General_
100_CS_AS_
KS_SC_UTF8
UTF-8 (65001)SimSim

Unicode, UTF-8 e UTF-16

O Unicode é uma tabela que define um número para cada caractér, compreendendo símbolos, algarismos e letras de diversos alfabetos. O número atribuído a um caractér é chamado de code point.

O UTF-8 e o UTF-16 são encodings que seguem o Unicode. Basicamente, são formas de armazenar esses números em bytes.

O UTF-16 usa 2 bytes para a maioria dos caractéres e 4 para aqueles acima do intervalo padrão. Esse encoding é usado nas strings da maioria das linguagens de programação.

O UTF-8 usa uma quantidade variável de bytes, de 1 a 4 por caractér. É o principal encoding da Internet.

Intervalo UnicodeGruposBytes por char, UTF-8Bytes por char, UTF-16
0x0000 a 0x007FAlfabeto latino básico, algarismos arábicos (0 a 9), símbolos básicos do teclado12
0x0080 a 0x07FFAlfabetos latino estendido (com acentos, cê-cedilha), grego, cirílico, árabe, hebraico22
0x0800 a 0xFFFFIdeogramas japoneses e chineses; símbolos variados; operadores matemáticos32
0x010000 a 0x10FFFFPictogramas de escritas antigas (ex.: hieróglifos egípcios); emojis; símbolos musicais44

A escolha do encoding afeta diretamente o tamanho usado para armazenamento de textos. Se a maioria dos caractéres estiver no intervalo de alfabeto latino básico, o UTF-8 é melhor, porque usa menos bytes do que o UTF-16; mas, se a escrita for asiática, o UTF-16 tem vantagem, pois o caractér ocupa 2 bytes, contra 3 do UTF-8.

A tabela abaixo mostra como um número Unicode é convertido para UTF-8 ou UTF-16, para cada intervalo acima.

Caractér de exemploCode point em binárioEm UTF-8Em UTF-16
P (0x0050)001100100011001000000000 00110010
Ω (0x03A9)00000011 1010100111001110 1010100100000011 10101001
(0x20AC)00100000 1010110011100010 10000010 1010110000100000 10101100
🐎 (0x1F40E)00000001 11110100 0000111011110000 10011111 10010000 1000111011011000 00111101 11011100 00001110

A lógica do UTF-16 para code points acima de 0x010000 é:

U = code point
W1 = 2 bytes superiores
W2 = 2 bytes inferiores

W = U - 0x10000 
W = yyyyyyyyyyxxxxxxxxxx (20 dígitos binários)
W1 = 110110yy yyyyyyyy
W2 = 110111xx xxxxxxxx

-> não há risco de W1 e W2 serem encarados como 
outros caracteres porque o intervalo possível deles 
é protegido na tabela Unicode.

Textos em bancos de dados SQL

CHAR e NCHAR recebem textos de tamanhos fixos; já o VARCHAR e o NVARCHAR aceitam tamanhos variáveis.

NCHAR e NVARCHAR são tipos presentes no SQL Server e o 'N' indica que eles armazenam o texto em codificação UTF-16. Já o CHAR e VARCHAR armazenam conforme o encoding da collation do banco.

Na declaração de tipo de coluna, é especificado o tamanho aceito por ela, por exemplo, NVARCHAR(n). É muito comum as pessoas pensarem que n é o tamanho em caractéres, mas isso não é verdade. Para CHAR e VARCHAR, n define o tamanho em bytes; para NCHAR e NVARCHAR, n é o tamanho em pares de bytes (x2).

Exemplo prático

Temos dois bancos de dados, um com collation Latin1_General_CI_AS (encoding Windows-1252) e outro com a collation Latin1_General_100_CI_AS_KS_SC_UTF8 (encoding UTF-8). Para cada um deles, vamos comparar o armazenamento entre VARCHAR e NVARCHAR, para textos em alfabeto latino básico, latino estendido, grego e emojis. Segue o script para executar:

CREATE TABLE [dbo].[Pessoa] (
  [Nome] VARCHAR(24) NOT NULL,
  [NomeUtf16] NVARCHAR(24) NOT NULL
);

INSERT INTO [dbo].[Pessoa] VALUES
('Pericles','Pericles'), -- latino sem acento
('Péricles','Péricles'), -- latino com acento
(N'Περικλῆς',N'Περικλῆς'), -- grego
(N'Papai Noel 🎅',N'Papai Noel 🎅'); -- com emoji
-- o prefixo N é necessário para strings unicode

SELECT
  [Nome], DATALENGTH([Nome]) AS [TamanhoEmBytes],
  [NomeUtf16], DATALENGTH([NomeUtf16]) AS [TamanhoEmBytes]
FROM [dbo].[Pessoa];

DROP TABLE [dbo].[Pessoa];

Latin1 General CI AS

Nome VARCHARTamanho em bytesNome NVARCHARTamanho em bytes
Pericles8Pericles16
Péricles8Péricles16
?e??????8Περικλῆς16
Papai Noel ??13Papai Noel 🎅26

Podemos perceber que o encoding Windows-1252 não suporta caractéres gregos e emojis, que são substituídos por '?'. Apesar disso, consegue atender muito bem palavras latinas, gastando apenas 1 byte por letra, mesmo naquelas com acentos ou cedilhas.

Latin1 General 100 CI AS KS SC UTF8

Nome VARCHARTamanho em bytesNome NVARCHARTamanho em bytes
Pericles8Pericles16
Péricles9Péricles16
Περικλῆς17Περικλῆς16
Papai Noel 🎅15Papai Noel 🎅26

Com a collation UTF-8, o campo VARCHAR suportou com sucesso emojis e caractéres gregos e teve maior eficiência em geral. O terceiro nome, Περικλῆς, precisou de 17 bytes porque a letra ῆ (unicode 0x1FC6) é do grego antigo e requer 3 bytes em UTF-8.

Fontes e leituras interessantes

A
AlexandreHTRB

Campinas/SP,
Brasil