Si está tratando con NVARCHAR
/ NCHAR
datos (que se almacenan como UTF-16 Little Endian ), entonces usaría el Unicode
codificación, no BigEndianUnicode
. En .NET, UTF-16 se llama Unicode
mientras que otras codificaciones Unicode se conocen por sus nombres reales:UTF7, UTF8 y UTF32. Por lo tanto, Unicode
por sí mismo es Little Endian
a diferencia de BigEndianUnicode
. ACTUALIZACIÓN: Consulte la sección al final sobre UCS-2 y caracteres complementarios.
En el lado de la base de datos:
SELECT HASHBYTES('MD5', N'è') AS [HashBytesNVARCHAR]
-- FAC02CD988801F0495D35611223782CF
En el lado de .NET:
System.Text.Encoding.ASCII.GetBytes("è")
// D1457B72C3FB323A2671125AEF3EAB5D
System.Text.Encoding.UTF7.GetBytes("è")
// F63A0999FE759C5054613DDE20346193
System.Text.Encoding.UTF8.GetBytes("è")
// 0A35E149DBBB2D10D744BF675C7744B1
System.Text.Encoding.UTF32.GetBytes("è")
// 86D29922AC56CF022B639187828137F8
System.Text.Encoding.BigEndianUnicode.GetBytes("è")
// 407256AC97E4C5AEBCA825DEB3D2E89C
System.Text.Encoding.Unicode.GetBytes("è") // this one matches HASHBYTES('MD5', N'è')
// FAC02CD988801F0495D35611223782CF
Sin embargo, esta pregunta pertenece a VARCHAR
/ CHAR
datos, que son ASCII, por lo que las cosas son un poco más complicadas.
En el lado de la base de datos:
SELECT HASHBYTES('MD5', 'è') AS [HashBytesVARCHAR]
-- 785D512BE4316D578E6650613B45E934
Ya vemos el lado .NET arriba. A partir de esos valores hash, debería haber dos preguntas:
- ¿Por qué no ninguna de ellos coinciden con los
HASHBYTES
valor? - ¿Por qué el artículo "sqlteam.com" vinculado en la respuesta de @Eric J. muestra que tres de ellos (
ASCII
,UTF7
yUTF8
) todos coinciden conHASHBYTES
valor?
Hay una respuesta que cubre ambas preguntas:las páginas de códigos. La prueba realizada en el artículo "sqlteam" usó caracteres ASCII "seguros" que están en el rango de 0 a 127 (en términos del valor int/decimal) que no varían entre las páginas de códigos. Pero el rango 128 - 255, donde encontramos el carácter "è", es el Extendido conjunto que varía según la página de códigos (lo cual tiene sentido ya que esta es la razón por la que se tienen páginas de códigos).
Ahora intenta:
SELECT HASHBYTES('MD5', 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes]
-- D1457B72C3FB323A2671125AEF3EAB5D
Que coincida con el ASCII
valor hash (y nuevamente, debido a que el artículo / prueba "sqlteam" usó valores en el rango de 0 a 127, no vieron ningún cambio al usar COLLATE
). Genial, ahora finalmente encontramos una manera de hacer coincidir VARCHAR
/ CHAR
datos. ¿Todo bien?
Bueno en realidad no. Echemos un vistazo, veamos lo que realmente estábamos procesando:
SELECT 'è' AS [TheChar],
ASCII('è') AS [TheASCIIvalue],
'è' COLLATE SQL_Latin1_General_CP1255_CI_AS AS [CharCP1255],
ASCII('è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [TheASCIIvalueCP1255];
Devoluciones:
TheChar TheASCIIvalue CharCP1255 TheASCIIvalueCP1255
è 232 ? 63
¿Un ?
? Solo para verificar, ejecuta:
SELECT CHAR(63) AS [WhatIs63?];
-- ?
Ah, entonces la página de códigos 1255 no tiene el è
carácter, por lo que se traduce como el ?
favorito de todos . Pero entonces, ¿por qué eso coincidió con el valor hash MD5 en .NET al usar la codificación ASCII? ¿Podría ser que en realidad no coincidiéramos con el valor hash de è
? , sino que coincidían con el valor hash de ?
:
SELECT HASHBYTES('MD5', '?') AS [HashBytesVARCHAR]
-- 0xD1457B72C3FB323A2671125AEF3EAB5D
Sí. El verdadero conjunto de caracteres ASCII es simplemente los primeros 128 caracteres (valores 0 - 127). Y como acabamos de ver, el è
es 232. Entonces, usando el ASCII
la codificación en .NET no es tan útil. Tampoco estaba usando COLLATE
en el lado de T-SQL.
¿Es posible obtener una mejor codificación en el lado .NET? Sí, mediante el uso de Encoding.GetEncoding(Int32), que permite especificar la página de códigos. La página de códigos a usar se puede descubrir usando la siguiente consulta (use sys.columns
cuando se trabaja con una columna en lugar de un literal o variable):
SELECT sd.[collation_name],
COLLATIONPROPERTY(sd.[collation_name], 'CodePage') AS [CodePage]
FROM sys.databases sd
WHERE sd.[name] = DB_NAME(); -- replace function with N'{db_name}' if not running in the DB
La consulta anterior devuelve (para mí):
Latin1_General_100_CI_AS_SC 1252
Por lo tanto, probemos la página de códigos 1252:
System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è')
// 785D512BE4316D578E6650613B45E934
¡Guau! Tenemos una coincidencia para VARCHAR
datos que utilizan nuestra intercalación predeterminada de SQL Server :). Por supuesto, si los datos provienen de una base de datos o un conjunto de campos en una intercalación diferente, entonces GetEncoding(1252)
podría no funcionará y tendrá que encontrar la página de códigos coincidente real utilizando la consulta que se muestra arriba (una página de códigos se usa en muchas intercalaciones, por lo que una intercalación diferente no necesariamente implica una página de códigos diferente).
Para ver cuáles son los posibles valores de la página de códigos y a qué cultura o región pertenecen, consulte la lista de páginas de códigos aquí (la lista se encuentra en la sección "Comentarios").
Información adicional relacionada con lo que realmente se almacena en NVARCHAR
/ NCHAR
campos:
Se puede almacenar cualquier carácter UTF-16 (2 o 4 bytes), aunque el comportamiento predeterminado de las funciones integradas asume que todos los caracteres son UCS-2 (2 bytes cada uno), que es un subconjunto de UTF-16. A partir de SQL Server 2012, es posible acceder a un conjunto de intercalaciones de Windows que admiten los caracteres de 4 bytes conocidos como caracteres complementarios. Usando una de estas intercalaciones de Windows que terminan en _SC
, ya sea especificado para una columna o directamente en una consulta, permitirá que las funciones integradas manejen adecuadamente los caracteres de 4 bytes.
-- The database's collation is set to: SQL_Latin1_General_CP1_CI_AS
SELECT N'𨝫' AS [SupplementaryCharacter],
LEN(N'𨝫') AS [LEN],
DATALENGTH(N'𨝫') AS [DATALENGTH],
UNICODE(N'𨝫') AS [UNICODE],
LEFT(N'𨝫', 1) AS [LEFT],
HASHBYTES('MD5', N'𨝫') AS [HASHBYTES];
SELECT N'𨝫' AS [SupplementaryCharacter],
LEN(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [LEN],
DATALENGTH(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [DATALENGTH],
UNICODE(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [UNICODE],
LEFT(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC, 1) AS [LEFT],
HASHBYTES('MD5', N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [HASHBYTES];
Devoluciones:
SupplementaryChar LEN DATALENGTH UNICODE LEFT HASHBYTES
𨝫 2 4 55393 � 0x7A04F43DA81E3150F539C6B99F4B8FA9
𨝫 1 4 165739 𨝫 0x7A04F43DA81E3150F539C6B99F4B8FA9
Como puedes ver, ni DATALENGTH
ni HASHBYTES
Son afectados. Para obtener más información, consulte la página de MSDN para Compatibilidad con intercalación y Unicode (específicamente la sección "Caracteres complementarios").