Il modo in cui un "carattere" (che può essere composto da più punti di codice: coppie surrogate, combinazione di caratteri, ecc.) Rispetto a un altro si basa su un insieme piuttosto complesso di regole. È così complesso a causa della necessità di tenere conto di tutte le varie (e talvolta "stravaganti") regole trovate in tutte le lingue rappresentate nella specifica Unicode . Questo sistema si applica alle regole di confronto non binarie per tutti i NVARCHAR
dati e per i VARCHAR
dati che utilizzano regole di confronto Windows e non SQL Server (una che inizia con SQL_
). Questo sistema non si applica ai VARCHAR
dati che utilizzano un confronto SQL Server in quanto utilizzano semplici mapping.
La maggior parte delle regole sono definite nell'Algoritmo di confronto Unicode (UCA) . Alcune di queste regole, e alcune non coperte dall'UCA, sono:
- L'ordinamento / peso predefinito indicato nel
allkeys.txt
file (indicato di seguito)
- Quali sensibilità e opzioni vengono utilizzate (ad esempio, è sensibile al maiuscolo / minuscolo o insensibile ?, e se sensibile, allora è maiuscolo o minuscolo prima?)
- Eventuali sostituzioni basate su locale.
- Viene utilizzata la versione dello standard Unicode.
- Il fattore "umano" (ovvero Unicode è una specifica, non un software, e viene quindi lasciato a ciascun fornitore per implementarlo)
Ho sottolineato quell'ultimo punto relativo al fattore umano per chiarire, si spera, che non ci si dovrebbe aspettare che SQL Server si comporti sempre al 100% in base alle specifiche.
Il fattore prevalente qui è la ponderazione data a ciascun punto di codice e il fatto che più punti di codice possono condividere la stessa specifica di peso. Puoi trovare i pesi di base (nessuna sostituzione specifica per le impostazioni locali) qui (credo che la 100
serie di regole di confronto sia Unicode v 5.0 - conferma informale nei commenti sull'elemento Microsoft Connect ):
http://www.unicode.org/Public/UCA/5.0.0/allkeys.txt
Il punto di codice in questione - U + FFFD - è definito come:
FFFD ; [*0F12.0020.0002.FFFD] # REPLACEMENT CHARACTER
Tale notazione è definita nella sezione 9.1 Formato file Allkeys dell'UCA:
<entry> := <charList> ';' <collElement>+ <eol>
<charList> := <char>+
<collElement> := "[" <alt> <weight> "." <weight> "." <weight> ("." <weight>)? "]"
<alt> := "*" | "."
Collation elements marked with a "*" are variable.
L'ultima riga è importante poiché il punto di codice che stiamo guardando ha una specifica che inizia effettivamente con "*". Nella sezione 3.6 Ponderazione delle variabili ci sono quattro possibili comportamenti definiti, basati sui valori di configurazione delle regole di confronto a cui non abbiamo accesso diretto (questi sono codificati nell'implementazione Microsoft di ogni confronto, ad esempio se la distinzione tra maiuscole e minuscole utilizza prima le lettere minuscole o prima in maiuscolo, una proprietà che è diversa tra i VARCHAR
dati che utilizzano le SQL_
regole di confronto e tutte le altre varianti).
Non ho tempo di fare la ricerca completa su quali percorsi vengono intrapresi e di dedurre quali opzioni vengono utilizzate in modo tale da poter fornire una prova più solida, ma è sicuro di dire che all'interno di ciascuna specifica di Code Point, indipendentemente dal fatto è considerato "uguale" non utilizzerà sempre la specifica completa. In questo caso, abbiamo "0F12.0020.0002.FFFD" e molto probabilmente sono in uso solo i livelli 2 e 3 (cioè .0020.0002. ). Fare un "Conteggio" in Notepad ++ per ".0020.0002." trova 12.581 partite (inclusi i personaggi supplementari che non abbiamo ancora affrontato). Fare un "Conteggio" su "[*" restituisce 4049 corrispondenze. Esecuzione di un "Trova" / "Conteggio" RegEx utilizzando un modello di\[\*\d{4}\.0020\.0002
restituisce 832 corrispondenze. Quindi da qualche parte in questa combinazione, oltre forse ad altre regole che non vedo, oltre ad alcuni dettagli di implementazione specifici di Microsoft, è la spiegazione completa di questo comportamento. E per essere chiari, il comportamento è lo stesso per tutti i personaggi corrispondenti in quanto si corrispondono tutti in quanto hanno tutti lo stesso peso una volta applicate le regole (il che significa che questa domanda avrebbe potuto essere posta su uno di essi, non necessariamente Mr. �
).
Puoi vedere con la query qui sotto e cambiare la COLLATE
clausola secondo i risultati sotto la query come funzionano le varie sensibilità tra le due versioni di Collations:
;WITH cte AS
(
SELECT TOP (65536) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS [Num]
FROM [master].sys.columns col
CROSS JOIN [master].sys.objects obj
)
SELECT cte.Num AS [Decimal],
CONVERT(VARBINARY(2), cte.Num) AS [Hex],
NCHAR(cte.Num) AS [Character]
FROM cte
WHERE NCHAR(cte.Num) = NCHAR(0xFFFD) COLLATE Latin1_General_100_CS_AS_WS --N'�'
ORDER BY cte.Num;
Di seguito sono riportati i vari conteggi dei personaggi corrispondenti in diverse regole di confronto.
Latin1_General_100_CS_AS_WS = 5840
Latin1_General_100_CS_AS = 5841 (The "extra" character is U+3000)
Latin1_General_100_CI_AS = 5841
Latin1_General_100_CI_AI = 6311
Latin1_General_CS_AS_WS = 21,229
Latin1_General_CS_AS = 21,230
Latin1_General_CI_AS = 21,230
Latin1_General_CI_AI = 21,537
In tutte le regole di confronto sopra elencate viene N'' = N'�'
anche considerato vero.
AGGIORNARE
Sono stato in grado di fare un po 'più di ricerca ed ecco cosa ho trovato:
Come "probabilmente" dovrebbe funzionare
Usando la Demo Collation ICU , ho impostato la locale su "en-US-u-va-posix", ho impostato la forza su "primaria", ho controllato mostra "chiavi di ordinamento" e incollato nei seguenti 4 caratteri che ho copiato dal risultati della query sopra (usando la Latin1_General_100_CI_AI
Collation):
�
Ԩ
ԩ
Ԫ
e che ritorna:
Ԫ
60 2E 02 .
Ԩ
60 7A .
ԩ
60 7A .
�
FF FD .
Quindi, controlla le proprietà del carattere per " " su http://unicode.org/cldr/utility/character.jsp?a=fffd e verifica che la chiave di ordinamento di livello 1 (ovvero FF FD
) corrisponda alla proprietà "uca". Facendo clic su quella proprietà "uca" si accede a una pagina di ricerca - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3DFFFD%3A%5D - che mostra solo 1 corrispondenza. E, nel file allkeys.txt , il peso di ordinamento di livello 1 viene mostrato come 0F12
, e c'è solo 1 corrispondenza per quello.
Per assicurarmi di interpretare correttamente il comportamento, ho esaminato un altro personaggio: LETTERA DI CAPITALE GRECO OMICRON CON VARIA Ὸ
su http://unicode.org/cldr/utility/character.jsp?a=1FF8 che ha un "uca" ( vale a dire peso di ordinamento di livello 1 / elemento di fascicolazione) di 5F30
. Cliccando su quel "5F30" ci porta ad una pagina di ricerca - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3D5F30%3A%5D - che mostra 30 partite, 20 di essendo compresi nell'intervallo 0 - 65535 (ovvero U + 0000 - U + FFFF). Guardando nel file allkeys.txt per Code Point 1FF8 , vediamo un peso di ordinamento di livello 1 di 12E0
. Fare un "Count" in Notepad ++ su12E0.
mostra 30 corrispondenze (corrisponde ai risultati di Unicode.org, sebbene non sia garantito poiché il file è per Unicode v 5.0 e il sito utilizza i dati Unicode v 9.0).
In SQL Server, la query seguente restituisce 20 corrispondenze, uguale alla ricerca Unicode.org quando si rimuovono i 10 caratteri supplementari:
;WITH cte AS
(
SELECT TOP (65535) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS [Num]
FROM [master].sys.columns col
CROSS JOIN [master].sys.objects obj
)
SELECT cte.Num AS [Decimal],
CONVERT(VARCHAR(50), CONVERT(VARBINARY(2), cte.Num), 2) AS [Hex],
NCHAR(cte.Num) AS [Character]
FROM cte
WHERE NCHAR(cte.Num) = NCHAR(0x1FF8) COLLATE Latin1_General_100_CI_AI
ORDER BY cte.Num;
E, per essere sicuri, tornando alla pagina Demo Collation ICU e sostituendo i caratteri nella casella "Input" con i seguenti 3 caratteri presi dall'elenco di 20 risultati da SQL Server:
Ὂ
𝜪
Ὸ
mostra che, in effetti, hanno tutti lo stesso 5F 30
peso di ordinamento di livello 1 (corrispondente al campo "uca" nella pagina delle proprietà del personaggio).
Quindi, sembra certamente che questo personaggio particolare non dovrebbe eguagliare nient'altro.
Come funziona effettivamente (almeno in Microsoft-land)
A differenza di SQL Server, .NET ha un mezzo per mostrare la chiave di ordinamento per una stringa tramite il metodo CompareInfo.GetSortKey . Utilizzando questo metodo e passando solo il carattere U + FFFD, restituisce una chiave di ordinamento di 0x0101010100
. Quindi, ripetendo tutti i personaggi nell'intervallo 0 - 65535 per vedere quali avevano una chiave di ordinamento di 0x0101010100
4529 partite restituite. Questo non corrisponde esattamente al 5840 restituito in SQL Server (quando si utilizza la Latin1_General_100_CS_AS_WS
raccolta), ma è il più vicino che possiamo ottenere (per ora) dato che sto eseguendo Windows 10 e .NET Framework versione 4.6.1, che utilizza Unicode v 6.3.0 secondo il grafico per la classe CharUnicodeInfo(in "Nota per i chiamanti", nella sezione "Osservazioni"). Per il momento sto usando una funzione SQLCLR e quindi non posso cambiare la versione del Framework di destinazione. Quando ne avrò la possibilità, creerò un'app console e userò una versione Framework 4.5 di destinazione in quanto utilizza Unicode v 5.0, che dovrebbe corrispondere alle regole di confronto della serie 100.
Ciò che questo test mostra è che, anche senza lo stesso numero esatto di corrispondenze tra .NET e SQL Server per U + FFFD, è abbastanza chiaro che non si tratta di un comportamento specifico di SQL Server e che sia intenzionale o responsabile dell'implementazione effettuata da Microsoft, il carattere U + FFFD corrisponde effettivamente a parecchi caratteri, anche se non dovrebbe essere conforme alla specifica Unicode. E, dato che questo personaggio corrisponde a U + 0000 (null), probabilmente è solo una questione di pesi mancanti.
ANCHE
Per quanto riguarda la differenza di comportamento nella =
query rispetto alla LIKE N'%�%'
query, ha a che fare con i caratteri jolly e i pesi mancanti (presumo) per questi (cioè � Ƕ Ƿ Ǹ
) caratteri. Se la LIKE
condizione viene modificata in semplicemente LIKE N'�'
, restituisce le stesse 3 righe della =
condizione. Se il problema con i caratteri jolly non è dovuto a pesi "mancanti" (non esiste 0x00
una chiave di ordinamento restituita da CompareInfo.GetSortKey
, tra l'altro), ciò potrebbe essere dovuto al fatto che questi caratteri possiedono potenzialmente una proprietà che consente alla chiave di ordinamento di variare in base al contesto (ovvero caratteri circostanti ).
FFFD
(la ricerca di*0F12.0020.0002.FFFD
restituisce solo un risultato). Dall'osservazione di @ Forrest che tutti corrispondono alla stringa vuota e che un po 'più di lettura sull'argomento sembra che il peso che condividono nelle varie regole di confronto non binarie sia in realtà zero, credo.