Atualmente, estou no processo de migração de dados do Oracle para o SQL Server e estou encontrando um problema ao tentar validar os dados pós-migração.
Detalhes do ambiente:
- Oracle 12 - conjunto de caracteres AL32UTF8
- Cliente - NLS_LANG - WE8MSWIN1252
- Campo VARCHAR2
SQL Server 2016
- Agrupamento Latin1_General_CI_AS
- Campo NVARCHAR
Estou usando DBMS_CRYPTO.HASH no Oracle para gerar uma soma de verificação de toda a linha, copiando para SQL e usando HASHBYTES para gerar uma soma de verificação de toda a linha, que estou comparando para validar as correspondências de dados.
As somas de verificação correspondem a todas as linhas, exceto aquelas com caracteres multibyte.
Por exemplo, linhas com este caractere: ◦ não correspondem nas somas de verificação, mesmo que os dados sejam transferidos corretamente. Quando uso DUMP no Oracle ou converto para VARBINARY no SQL Server, os dados correspondem exatamente, exceto os bytes desse caractere.
No SQL Server, os bytes são 0xE625 e no Oracle são 0x25E6.
Por que eles são ordenados de forma diferente e existe uma maneira confiável de converter um para o outro para garantir que a soma de verificação na outra extremidade corresponda a strings com caracteres de vários bytes?
O agrupamento de uma coluna
NVARCHAR
/NCHAR
/NTEXT
não tem relação com a codificação usada para armazenar os dados nessa coluna.NVARCHAR
os dados são sempre UTF-16 Little Endian (LE). O agrupamento deNVARCHAR
dados afeta apenas a classificação e a comparação. O agrupamento afeta a codificação dosVARCHAR
dados, pois o agrupamento determina a página de código usada para armazenar os dados nessa coluna/variável/literal, mas não estamos lidando com isso aqui.Como sepupic mencionou , o que você está vendo quando visualiza os dados em formato binário é uma diferença de endianness (Oracle está usando Big Endian enquanto o SQL Server está usando Little Endian). NO ENTANTO, o que você está vendo quando visualiza a forma binária da string no Oracle não é como os dados estão realmente sendo armazenados. Você está usando
AL32UTF8
o que é UTF-8, que codifica esse caractere em 3 bytes, não em 2, como:E2, 97, A6
.Além disso, não é possível que os hashes sejam os mesmos para linhas de apenas "a", mas não quando incluem "◦", a menos que o hash no Oracle tenha sido feito sem conversão, portanto, usando a codificação UTF-8 e o hash no SQL Server convertendo acidentalmente para
VARCHAR
primeiro. Caso contrário, não há algoritmo de hash que se comporte como você está descrevendo, como você pode verificar executando o seguinte no SQL Server:No Oracle, você deve usar a
CONVERT
função para obter a string naAL16UTF16LE
codificação e, em seguida, fazer o hash desse valor. Isso deve corresponder ao que o SQL Server tem. Por exemplo, você pode ver as diferentes formas de codificação do White Bullet (U + 25E6) e como usarCONVERT
junto comAL16UTF16LE
corrigirá isso no dbfiddle e abaixo:Que retorna:
Como você pode ver na 3ª coluna, o conjunto de caracteres é relatado erroneamente como sendo Big Endian quando é claramente Little Endian com base na ordem dos dois bytes. Você também pode ver que ambos os caracteres são dois bytes em UTF-16, e a ordem de ambos é diferente entre Big e Little Endian, não apenas os caracteres que são > 1 byte em UTF-8.
Dado tudo isso, como os dados estão sendo armazenados como UTF-8, mas você os está vendo como UTF-16 Big Endian por meio da
DUMP
função, parece que você já está convertendo para UTF-16, mas provavelmente não percebendo que o padrão UTF-16 no Oracle é Big Endian.Observando a definição "UTF-16" na página Glossário da documentação do Oracle , ela afirma (dividi as seguintes frases em duas partes para que seja mais fácil distinguir entre BE e LE):
e:
PS Como você está usando
AL32UTF8
no Oracle, você deve usar oLatin1_General_100_CI_AS_SC
agrupamento no SQL Server, não noLatin1_General_CI_AS
. O que você está usando é mais antigo e não suporta totalmente caracteres suplementares (sem perda de dados se existirem, mas as funções internas os tratam como 2 caracteres em vez de uma única entidade).O que você está vendo é uma
Little-Endian
codificação queSQL Server
usa para armazenarUnicode
caracteres (mais precisamente, usaUCS-2 LE
).Mais
Little-Endian
aqui: Diferença entre a ordem Big Endian e Little Endian ByteEu não sei como foi possível isso
Todos os
Unicode
caracteres armazenados emSQL Server
, convertidos embinary
, são "invertidos", quer dizer, para ver os códigos reais você deve dividi-los em grupos de 2bytes
e inverter a ordem dentro de cada par.Exemplo:
O resultado é
Como você vê no caso de
Unicode
bytes de caracteres serem invertidos: "a" é representado como0x6100
e não como0x0061
.A mesma história é sobre o código
0x25E6
real enquanto em representação você o vê como , ou seja .Unicode
binary
SQL Server
0xE625
inverted