Jeśli masz do czynienia z NVARCHAR
/ NCHAR
dane (które są przechowywane jako UTF-16 Little Endian ), wtedy użyjesz Unicode
kodowanie, a nie BigEndianUnicode
. W .NET, UTF-16 nazywa się Unicode
podczas gdy inne kodowania Unicode są określane przez ich rzeczywiste nazwy:UTF7, UTF8 i UTF32. Stąd Unicode
sam w sobie jest Little Endian
w przeciwieństwie do BigEndianUnicode
. AKTUALIZACJA: Proszę zapoznać się z sekcją na końcu dotyczącą UCS-2 i znaków uzupełniających.
Po stronie bazy danych:
SELECT HASHBYTES('MD5', N'è') AS [HashBytesNVARCHAR]
-- FAC02CD988801F0495D35611223782CF
Po stronie .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
Jednak to pytanie dotyczy VARCHAR
/ CHAR
dane, które są ASCII, więc sprawy są nieco bardziej skomplikowane.
Po stronie bazy danych:
SELECT HASHBYTES('MD5', 'è') AS [HashBytesVARCHAR]
-- 785D512BE4316D578E6650613B45E934
Widzimy już stronę .NET powyżej. Z tych zaszyfrowanych wartości powinny wynikać dwa pytania:
- Dlaczego nie żadne z nich pasuje do
HASHBYTES
wartość? - Dlaczego artykuł „sqlteam.com” połączony w odpowiedzi @Eric J. pokazuje, że trzy z nich (
ASCII
,UTF7
iUTF8
) wszystkie pasują doHASHBYTES
wartość?
Jest jedna odpowiedź, która obejmuje oba pytania:strony kodowe. W teście przeprowadzonym w artykule „sqlteam” użyto „bezpiecznych” znaków ASCII z zakresu od 0 do 127 (pod względem wartości int/dziesiętnych), które nie różnią się między stronami kodowymi. Ale zakres 128-255 – w którym znajdujemy znak „è” – to Rozszerzony zestaw, który różni się w zależności od strony kodowej (co ma sens, ponieważ jest to powód posiadania stron kodowych).
Teraz spróbuj:
SELECT HASHBYTES('MD5', 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes]
-- D1457B72C3FB323A2671125AEF3EAB5D
To pasuje do ASCII
zahaszowana wartość (i ponownie, ponieważ artykuł / test "sqlteam" używał wartości z zakresu od 0 do 127, nie zauważyli żadnych zmian podczas używania COLLATE
). Świetnie, teraz w końcu znaleźliśmy sposób na dopasowanie VARCHAR
/ CHAR
dane. Wszystko w porządku?
Cóż, nie za bardzo. Przyjrzyjmy się, co właściwie haszowaliśmy:
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];
Zwroty:
TheChar TheASCIIvalue CharCP1255 TheASCIIvalueCP1255
è 232 ? 63
?
? Aby zweryfikować, uruchom:
SELECT CHAR(63) AS [WhatIs63?];
-- ?
Ach, więc strona kodowa 1255 nie zawiera è
znak, więc zostanie przetłumaczony jako ulubiony ?
. Ale w takim razie dlaczego to pasowało do wartości skrótu MD5 w .NET podczas korzystania z kodowania ASCII? Czy to możliwe, że tak naprawdę nie pasowaliśmy do zahaszowanej wartości è
, ale zamiast tego pasowały do zahaszowanej wartości ?
:
SELECT HASHBYTES('MD5', '?') AS [HashBytesVARCHAR]
-- 0xD1457B72C3FB323A2671125AEF3EAB5D
Tak. Prawdziwy zestaw znaków ASCII to tylko pierwsze 128 znaków (wartości od 0 do 127). I jak właśnie widzieliśmy, è
wynosi 232. Tak więc, używając ASCII
kodowanie w .NET nie jest tak pomocne. Nie używałem też COLLATE
po stronie T-SQL.
Czy można uzyskać lepsze kodowanie po stronie .NET? Tak, przy użyciu Encoding.GetEncoding(Int32), który umożliwia określenie strony kodowej. Używaną stronę kodową można wykryć za pomocą następującego zapytania (użyj sys.columns
podczas pracy z kolumną zamiast literałem lub zmienną):
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
Powyższe zapytanie zwraca (dla mnie):
Latin1_General_100_CI_AS_SC 1252
Wypróbujmy więc stronę kodową 1252:
System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è')
// 785D512BE4316D578E6650613B45E934
Hura! Mamy dopasowanie dla VARCHAR
dane, które używają naszego domyślnego sortowania SQL Server :). Oczywiście, jeśli dane pochodzą z bazy danych lub pola ustawionego na inne sortowanie, to GetEncoding(1252)
może nie działa i będziesz musiał znaleźć pasującą stronę kodową za pomocą zapytania pokazanego powyżej (strona kodowa jest używana w wielu sortowaniach, więc inne sortowanie nie koniecznie oznaczać inną stronę kodową).
Aby zobaczyć, jakie są możliwe wartości strony kodowej i jakiej kultury/lokalizacji one dotyczą, zapoznaj się z listą stron kodowych tutaj (lista znajduje się w sekcji „Uwagi”).
Dodatkowe informacje związane z tym, co jest faktycznie przechowywane w NVARCHAR
/ NCHAR
pola:
Można przechowywać dowolny znak UTF-16 (2 lub 4 bajty), chociaż domyślne zachowanie funkcji wbudowanych zakłada, że wszystkie znaki to UCS-2 (każdy po 2 bajty), który jest podzbiorem UTF-16. Począwszy od SQL Server 2012, możliwy jest dostęp do zestawu sortowania systemu Windows, który obsługuje 4-bajtowe znaki znane jako znaki uzupełniające. Korzystanie z jednego z tych sortowań Windows kończących się na _SC
, określony dla kolumny lub bezpośrednio w zapytaniu, umożliwi wbudowanym funkcjom prawidłową obsługę 4-bajtowych znaków.
-- 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];
Zwroty:
SupplementaryChar LEN DATALENGTH UNICODE LEFT HASHBYTES
𨝫 2 4 55393 � 0x7A04F43DA81E3150F539C6B99F4B8FA9
𨝫 1 4 165739 𨝫 0x7A04F43DA81E3150F539C6B99F4B8FA9
Jak widać, ani DATALENGTH
ani HASHBYTES
są dotknięte. Więcej informacji można znaleźć na stronie MSDN dotyczącej obsługi sortowania i Unicode (w szczególności w sekcji „Znaki uzupełniające”).