Perché la ricerca di LIKE N '% %' corrisponde a qualsiasi carattere Unicode e = N' 'corrisponde a molti?


21
DECLARE @T TABLE(
  Col NCHAR(1));

INSERT INTO @T
VALUES      (N'A'),
            (N'B'),
            (N'C'),
            (N'Ƕ'),
            (N'Ƿ'),
            (N'Ǹ');

SELECT *
FROM   @T
WHERE  Col LIKE N'%�%'

ritorna

Col
A
B
C
Ƕ
Ƿ
Ǹ

SELECT *
FROM   @T
WHERE  Col = N'�' 

ritorna

Col
Ƕ
Ƿ
Ǹ

La generazione di ogni possibile "carattere" a doppio byte con il seguente indica che la =versione corrisponde a 21.229 di essi e alla LIKE N'%�%'versione di tutti (ho provato alcune regole di confronto non binarie con lo stesso risultato).

WITH T(I, N)
AS 
(
SELECT TOP 65536 ROW_NUMBER() OVER (ORDER BY @@SPID),
                 NCHAR(ROW_NUMBER() OVER (ORDER BY @@SPID))
FROM master..spt_values v1, 
     master..spt_values v2
)
SELECT I, N 
FROM T
WHERE N = N'�'  

Qualcuno in grado di far luce su ciò che sta succedendo qui?

L'utilizzo COLLATE Latin1_General_BINcorrisponde quindi al singolo carattere NCHAR(65533), ma la domanda è capire quali regole utilizza nell'altro caso. Cosa c'è di speciale in quei 21.229 caratteri che corrispondono al =e perché tutto corrisponde al carattere jolly? Presumo ci sia qualche motivo dietro che mi manchi.

nchar(65534)[e altri 21k] funzionano altrettanto bene nchar(65533). La domanda avrebbe potuto essere formulata usando nchar(502) ugualmente come : si comporta allo stesso modo di LIKE N'%Ƕ%'(corrisponde a tutto) e nel =caso. Questo è probabilmente un grande indizio.

La modifica SELECTdell'ultima query per SELECT I, N, RANK() OVER(ORDER BY N)mostrare che SQL Server non può classificare i caratteri. Sembra che qualsiasi personaggio non gestito dalla collazione sia considerato equivalente.

Un database con Latin1_General_100_CS_ASregole di confronto produce 5840 corrispondenze. Latin1_General_100_CS_ASabbassa le =partite abbastanza considerevolmente, ma non cambia il LIKEcomportamento. Sembra che ci sia un gruppo di personaggi che è diventato più piccolo nelle successive regole di confronto che sono tutti uguali e vengono ignorati nelle LIKEricerche con caratteri jolly .

Sto usando SQL Server 2016. Il simbolo è il carattere sostitutivo Unicode, ma gli unici caratteri non validi nella codifica UCS-2 sono 55296 - 57343 AFAIK e corrisponde chiaramente a punti di codice perfettamente validi come quelli N'Ԛ'che non rientrano in questo intervallo.

Tutti questi personaggi si comportano come la stringa vuota per LIKEe =. Valutano persino come equivalenti. N'' = N'�'è vero e puoi rilasciarlo in un LIKEconfronto di singoli spazi LIKE '_' + nchar(65533) + '_'senza alcun effetto. LENi confronti producono risultati diversi, quindi probabilmente sono solo alcune funzioni di stringa.

Penso che il LIKEcomportamento sia corretto per questo caso; si comporta come un valore sconosciuto (che potrebbe essere qualsiasi cosa). Succede anche per questi altri personaggi:

  • nchar(11217) (Segno di incertezza)
  • nchar(65532) (Carattere di sostituzione oggetto)
  • nchar(65533) (Personaggio sostitutivo)
  • nchar(65534) (Non un personaggio)

Quindi, se voglio trovare tutti i caratteri che rappresentano l'incertezza con lo stesso segno, userei un confronto che supporta caratteri supplementari come Latin1_General_100_CI_AS_SC.

Immagino che questi siano il gruppo di "personaggi non ponderati" menzionati nella documentazione, Collation e Unicode Support .

Risposte:


9

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 NVARCHARdati e per i VARCHARdati che utilizzano regole di confronto Windows e non SQL Server (una che inizia con SQL_). Questo sistema non si applica ai VARCHARdati 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:

  1. L'ordinamento / peso predefinito indicato nel allkeys.txtfile (indicato di seguito)
  2. Quali sensibilità e opzioni vengono utilizzate (ad esempio, è sensibile al maiuscolo / minuscolo o insensibile ?, e se sensibile, allora è maiuscolo o minuscolo prima?)
  3. Eventuali sostituzioni basate su locale.
  4. Viene utilizzata la versione dello standard Unicode.
  5. 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 100serie 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 VARCHARdati 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\.0002restituisce 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 COLLATEclausola 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_AICollation):

�
Ԩ
ԩ
Ԫ

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 30peso 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 0x01010101004529 partite restituite. Questo non corrisponde esattamente al 5840 restituito in SQL Server (quando si utilizza la Latin1_General_100_CS_AS_WSraccolta), 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 LIKEcondizione 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 0x00una 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 ).


Grazie - in allkeys.txt collegato sembra che non ci sia nient'altro dato lo stesso peso di FFFD(la ricerca di *0F12.0020.0002.FFFDrestituisce 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.
Martin Smith,

1
@MartinSmith Ha fatto alcune ricerche usando la Demo Collation ICU , inserendo � A a \u24D0e alcuni altri che erano nel set di risultati delle partite 5839. Sembra che non si possa saltare il primo peso e questo carattere sostitutivo è l'unico a partire da 0F12. Molti altri avevano anche un primo peso unico, e molti mancavano del tutto dal file allkeys. Quindi questo potrebbe essere un bug di implementazione a causa di un errore umano. Ho visto questo carattere nel gruppo "non supportato" sul sito Unicode nei grafici delle regole di confronto. Guarderà di più domani.
Solomon Rutzky,

Rextester utilizza 4.5. In realtà vedo meno corrispondenze su quella versione (3385). Forse sto impostando qualche opzione diversa da te? rextester.com/JBWIN31407
Martin Smith,

A proposito, questa chiave di ordinamento 01 01 01 01 00è menzionata qui archives.miloush.net/michkap/archive/2007/09/10/4847780.html (sembra CompareInfo.InternalGetSortKeychiamate LCMapStringEx)
Martin Smith

@MartinSmith Ci ho giocato un po 'ma non sono sicuro di quale sia la differenza. Il sistema operativo su cui è in esecuzione .NET prende in considerazione. Guarderò più domani se avrò tempo. Indipendentemente dal numero di corrispondenze, tuttavia, ciò sembra almeno confermare il motivo del comportamento, soprattutto ora che abbiamo una visione approfondita della struttura chiave di ordinamento grazie al blog a cui ti sei collegato e ad alcuni altri collegati in esso. La pagina CharUnicodeInfo che ho collegato menziona le chiamate di confronto sottostanti, che è la base per il mio suggerimento qui: connect.microsoft.com/SQLServer/feedback/details/2932336 :-)
Solomon Rutzky
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.