Saltar al contenido

Orden de bytes para caracteres multibyte en SQL Server frente a Oracle

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 / SQL UTL_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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *