Solución:
Lo que estas viendo es Little-Endian
codificando eso SQL Server
utiliza para almacenar Unicode
caracteres (más precisamente, utiliza UCS-2 LE
).
Más en Little-Endian
aquí: Diferencia entre el orden de bytes Big Endian y Little Endian
No se como fue posible que
Cuando uso DUMP en Oracle o convierto a VARBINARY en SQL Server los datos coinciden exactamente a excepción de los bytes de este carácter
Todos los Unicode
caracteres almacenados en SQL Server
, convertido a binary
, están “invertidos”, quiero decir, para ver los códigos reales debes dividirlos en grupos de 2 bytes
e invertir el orden dentro de cada par.
Ejemplo:
declare @str varchar(3) = 'abc';
declare @str_n nvarchar(3) = N'abc';
select cast(@str as varbinary(3));
select cast(@str_n as varbinary(6));
El resultado es
0x616263
0x610062006300
Como ves en el caso de Unicode
caracteres bytes están invertidos: “a” se representa como 0x6100
y no como 0x0061
.
La misma historia se trata de 0x25E6
eso es real Unicode
código mientras está en binary
representación en SQL Server
lo ves como 0xE625
, es decir inverted
.
La colación de un NVARCHAR
/ NCHAR
/ NTEXT
column no influye en la codificación utilizada para almacenar los datos en esa columna. NVARCHAR
los datos son siempre UTF-16 Little Endian (LE). La colación de NVARCHAR
los datos solo afectan la clasificación y la comparación. La intercalación afecta la codificación de VARCHAR
datos ya que la intercalación determina la página de códigos utilizada para almacenar los datos en esa columna / variable / literal, pero no estamos tratando con eso aquí.
Como mencionó sepupic, lo que está viendo cuando ve los datos en forma binaria es una diferencia en el endianness (Oracle usa Big Endian mientras que SQL Server usa Little Endian). SIN EMBARGO, lo que está viendo cuando ve la forma binaria del string en Oracle es no cómo se almacenan realmente los datos. Tu estas usando AL32UTF8
que es UTF-8, que codifica ese carácter en 3 bytes, no 2, como: E2, 97, A6
.
Además, no es posible que los hash sean los mismos para las filas de solo “a”, pero no cuando incluyen “◦”, a menos que el hash en Oracle se haya realizado sin conversión, por lo tanto, utilizando la codificación UTF-8 y la hash en SQL Server convirtiéndose accidentalmente a VARCHAR
primero. De lo contrario, no hay un algoritmo hash que se comporte como lo describe, como puede verificar ejecutando lo siguiente en SQL Server:
DECLARE @Algorithm NVARCHAR(50) = N'MD4';
SELECT HASHBYTES(@Algorithm, 0x3100), HASHBYTES(@Algorithm, 0x0031);
SET @Algorithm = N'MD5';
SELECT HASHBYTES(@Algorithm, 0x3100), HASHBYTES(@Algorithm, 0x0031);
SET @Algorithm = N'SHA1';
SELECT HASHBYTES(@Algorithm, 0x3100), HASHBYTES(@Algorithm, 0x0031);
SET @Algorithm = N'SHA2_256';
SELECT HASHBYTES(@Algorithm, 0x3100), HASHBYTES(@Algorithm, 0x0031);
SET @Algorithm = N'SHA2_512';
SELECT HASHBYTES(@Algorithm, 0x3100), HASHBYTES(@Algorithm, 0x0031);
En Oracle, debe usar el CONVERT
función para obtener el string en el AL16UTF16LE
codificación, y luego hash ese valor. Eso debería coincidir con lo que tiene SQL Server. Por ejemplo, puede ver las diferentes formas de codificación de White Bullet (U + 25E6) y cómo usar CONVERT
junto con AL16UTF16LE
corregirá esto en dbfiddle y a continuación:
SELECT DUMP(CHR(14849958), 1016) AS "UTF8",
DUMP(CHR(9702 USING NCHAR_CS), 1016) AS "UTF16BE",
DUMP(CONVERT(CHR(9702 USING NCHAR_CS), 'AL16UTF16LE' ), 1016) AS "UTF16LE"
FROM DUAL;
SELECT DUMP('a' || CHR(14849958), 1016) AS "UTF8",
DUMP('a' || CHR(9702 USING NCHAR_CS), 1016) AS "UTF16BE",
DUMP(CONVERT('a' || CHR(9702 USING NCHAR_CS), 'AL16UTF16LE' ), 1016) AS "UTF16LE"
FROM DUAL;
Que vuelve:
UTF8: Typ=1 Len=3 CharacterSet=AL32UTF8: e2,97,a6
UTF16BE: Typ=1 Len=2 CharacterSet=AL16UTF16: 25,e6
UTF16LE: Typ=1 Len=2 CharacterSet=AL16UTF16: e6,25
UTF8: Typ=1 Len=4 CharacterSet=AL32UTF8: 61,e2,97,a6
UTF16BE: Typ=1 Len=4 CharacterSet=AL16UTF16: 0,61,25,e6
UTF16LE: Typ=1 Len=4 CharacterSet=AL16UTF16: 61,0,e6,25
Como puede ver en la tercera columna, el conjunto de caracteres se informa erróneamente como Big Endian cuando es claramente Little Endian según el orden de los dos bytes. También puede ver que ambos caracteres son dos bytes en UTF-16, y el orden de ambos de ellos es diferente entre Big y Little Endian, no solo los caracteres que son> 1 byte en UTF-8.
Dado todo esto, dado que los datos se almacenan como UTF-8, sin embargo, los ve como UTF-16 Big Endian a través del DUMP
, parece que ya lo está convirtiendo a UTF-16, pero probablemente sin darse cuenta de que el UTF-16 predeterminado en Oracle es Big Endian.
Mirando la definición de “UTF-16” en la página del Glosario de la documentación de Oracle, dice (dividí las siguientes oraciones en dos partes para que sea más fácil distinguir entre BE y LE):
AL16UTF16 implementa el esquema de codificación big-endian de la forma de codificación UTF-16 (el byte más significativo de cada unidad de código viene primero en la memoria). AL16UTF16 es un juego de caracteres nacional válido.
y:
AL16UTF16LE implementa el esquema de codificación little-endian UTF-16. Es un juego de caracteres de solo conversión, válido solo en funciones de conversión de juegos de caracteres como SQL
CONVERT
o PL / SQLUTL_I18N.STRING_TO_RAW
.
PD: ya que estás usando AL32UTF8
en Oracle, debería utilizar el Latin1_General_100_CI_AS_SC
intercalación en SQL Server, no Latin1_General_CI_AS
. El que está utilizando es más antiguo y no es totalmente compatible con los caracteres suplementarios (no hay pérdida de datos si existen, pero las funciones integradas los manejan como 2 caracteres en lugar de una sola entidad).
Si aceptas, tienes la habilidad dejar una crónica acerca de qué le añadirías a esta sección.