Warum stimmt die Suche nach LIKE N '% %' mit einem Unicode-Zeichen überein und = N' 'mit vielen?

20
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'%�%'

Kehrt zurück

Col
A
B
C
Ƕ
Ƿ
Ǹ

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

Kehrt zurück

Col
Ƕ
Ƿ
Ǹ

Das Generieren jedes möglichen =Doppelbyte- "Zeichens" mit dem Folgenden zeigt, dass die Version 21.229 von ihnen und die LIKE N'%�%'Version alle von ihnen übereinstimmt (ich habe einige nicht-binäre Kollatierungen mit demselben Ergebnis versucht).

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'�'  

Kann jemand Aufschluss darüber geben, was hier vor sich geht?

Die Verwendung von COLLATE Latin1_General_BINthen entspricht dem einzelnen Zeichen. NCHAR(65533)Die Frage ist jedoch, welche Regeln im anderen Fall verwendet werden. Was ist das Besondere an den 21.229 Zeichen, die mit dem übereinstimmen, =und warum stimmt alles mit dem Platzhalter überein? Ich nehme an, dass es einen Grund dafür gibt, dass ich vermisse.

nchar(65534)[und 21k andere] funktionieren genauso gut wie nchar(65533). Die Frage hätte nchar(502) genauso formuliert werden können wie - sie verhält sich genauso wie LIKE N'%Ƕ%'(passt zu allem) und im =Fall. Das ist wahrscheinlich ein ziemlich großer Hinweis.

Durch Ändern von SELECTin der letzten Abfrage wird angezeigt, SELECT I, N, RANK() OVER(ORDER BY N)dass SQL Server die Zeichen nicht einordnen kann. Es scheint, dass jedes Zeichen, das von der Kollatierung nicht behandelt wird, als gleichwertig betrachtet wird.

Eine Datenbank mit einer Latin1_General_100_CS_ASKollatierung erzeugt 5840 Übereinstimmungen. Latin1_General_100_CS_ASschneidet die =Streichhölzer ziemlich stark ab, ändert aber nichts am LIKEVerhalten. Es scheint, als gäbe es einen Topf mit Zeichen, der in späteren Kollatierungen kleiner geworden ist, die alle gleich sind und dann bei Platzhaltersuchen ignoriert werden LIKE.

Ich verwende SQL Server 2016. Das Symbol ist das Unicode-Ersetzungszeichen, aber die einzigen ungültigen Zeichen in der UCS-2-Codierung sind 55296 - 57343 AFAIK, und es stimmt eindeutig mit perfekt gültigen Codepunkten überein N'Ԛ', die nicht in diesem Bereich liegen.

Alle diese Zeichen verhalten sich wie die leere Zeichenfolge für LIKEund =. Sie bewerten sogar als gleichwertig. N'' = N'�'ist wahr, und Sie können es in einem LIKEVergleich einzelner Räume LIKE '_' + nchar(65533) + '_'ohne Auswirkung fallen lassen. LENVergleiche führen jedoch zu unterschiedlichen Ergebnissen, sodass es sich wahrscheinlich nur um bestimmte Zeichenfolgenfunktionen handelt.

Ich denke, dass das LIKEVerhalten für diesen Fall korrekt ist; es verhält sich wie ein unbekannter Wert (der alles sein kann). Es passiert auch für diese anderen Charaktere:

  • nchar(11217) (Unsicherheitszeichen)
  • nchar(65532) (Objektersetzungszeichen)
  • nchar(65533) (Ersatzzeichen)
  • nchar(65534) (Kein Charakter)

Wenn ich also alle Zeichen finden möchte, die eine Unsicherheit mit dem Gleichheitszeichen darstellen, würde ich eine Kollatierung verwenden, die zusätzliche Zeichen wie unterstützt Latin1_General_100_CI_AS_SC.

Ich denke, dies ist die Gruppe von "nicht gewichteten Zeichen", die in der Dokumentation, der Sortierung und der Unicode-Unterstützung erwähnt wird .

Martin Smith
quelle

Antworten:

8

Wie ein "Zeichen" (das aus mehreren Codepunkten bestehen kann: Ersatzpaare, Kombinieren von Zeichen usw.) mit einem anderen verglichen wird, basiert auf einem ziemlich komplexen Regelwerk. Es ist so komplex, weil all die verschiedenen (und manchmal "verrückten") Regeln berücksichtigt werden müssen, die in allen in der Unicode- Spezifikation dargestellten Sprachen zu finden sind. Dieses System gilt für nicht-binäre Kollatierungen für alle NVARCHARDaten und für VARCHARDaten, die eine Windows-Kollatierung und keine SQL Server-Kollatierung verwenden (eine beginnend mit SQL_). Dieses System gilt nicht für VARCHARDaten, die eine SQL Server-Kollatierung verwenden, da diese einfache Zuordnungen verwenden.

Die meisten Regeln sind im Unicode Collation Algorithm (UCA) definiert . Einige dieser Regeln und einige, die nicht in der UCA enthalten sind, sind:

  1. Die in der allkeys.txtDatei angegebene Standardreihenfolge / das Standardgewicht (siehe unten)
  2. Welche Sensitivitäten und Optionen werden verwendet (zB ist es case sensitive oder insensitive? Und wenn sensitiv, ist es zuerst groß oder zuerst klein?)
  3. Alle auf dem Gebietsschema basierenden Überschreibungen.
  4. Die Version des Unicode-Standards wird verwendet.
  5. Der Faktor "Mensch" (dh Unicode ist eine Spezifikation, keine Software, und es ist daher jedem Anbieter überlassen, sie zu implementieren)

Ich habe diesen letzten Punkt in Bezug auf den menschlichen Faktor betont, um hoffentlich klar zu machen, dass man nicht erwarten sollte, dass sich SQL Server immer zu 100% gemäß der Spezifikation verhält.

Der übergeordnete Faktor hierbei ist die Gewichtung jedes Codepunkts und die Tatsache, dass mehrere Codepunkte dieselbe Gewichtungsspezifikation haben können. Die Grundgewichte (keine länderspezifischen Überschreibungen) finden Sie hier (ich glaube, die 100Kollatierungsserie ist Unicode v 5.0 - informelle Bestätigung in den Kommentaren zum Microsoft Connect-Element ):

http://www.unicode.org/Public/UCA/5.0.0/allkeys.txt

Der betreffende Codepunkt - U + FFFD - ist wie folgt definiert:

FFFD  ; [*0F12.0020.0002.FFFD] # REPLACEMENT CHARACTER

Diese Notation ist in Abschnitt 9.1 Allkeys-Dateiformat der UCA definiert:

<entry>       := <charList> ';' <collElement>+ <eol>
<charList>    := <char>+
<collElement> := "[" <alt> <weight> "." <weight> "." <weight> ("." <weight>)? "]"
<alt>         := "*" | "."

Collation elements marked with a "*" are variable.

Diese letzte Zeile ist wichtig, da der Code Point, den wir betrachten, eine Spezifikation hat, die in der Tat mit "*" beginnt. In Abschnitt 3.6 Variable Gewichtung werden vier mögliche Verhaltensweisen definiert, die auf den Kollatierungskonfigurationswerten basieren, auf die wir keinen direkten Zugriff haben (diese sind in der Microsoft-Implementierung jeder Kollatierung fest codiert, z. B. ob bei Groß- und Kleinschreibung zuerst Kleinbuchstaben verwendet werden oder zuerst in Großbuchstaben, eine Eigenschaft, die sich zwischen VARCHARDaten, die SQL_Kollatierungen verwenden, und allen anderen Variationen unterscheidet).

Ich habe keine Zeit, mich eingehend mit der Frage zu befassen, welche Pfade eingeschlagen werden und welche Optionen verwendet werden, damit ein sicherer Beweis erbracht werden kann, aber man kann mit Sicherheit sagen, dass in jeder Codepunktspezifikation unabhängig davon, ob dies der Fall ist oder nicht gilt als "gleich" wird nicht immer die vollständige Spezifikation verwenden. In diesem Fall haben wir "0F12.0020.0002.FFFD" und höchstwahrscheinlich werden nur die Ebenen 2 und 3 verwendet (dh .0020.0002. ). Ausführen einer "Zählung" in Notepad ++ für ".0020.0002" findet 12.581 Übereinstimmungen (einschließlich zusätzlicher Zeichen, mit denen wir uns noch nicht befasst haben). Wenn Sie bei "[*" eine "Zählung" durchführen, werden 4049 Übereinstimmungen zurückgegeben. Durchführen eines RegEx "Find" / "Count" unter Verwendung eines Musters von\[\*\d{4}\.0020\.0002liefert 832 Treffer. Irgendwo in dieser Kombination, plus möglicherweise einigen anderen Regeln, die ich nicht sehe, plus einigen Microsoft-spezifischen Implementierungsdetails, ist die vollständige Erklärung dieses Verhaltens. Und um klar zu sein, ist das Verhalten für alle übereinstimmenden Zeichen gleich, da alle übereinstimmenden Zeichen das gleiche Gewicht haben, sobald die Regeln angewendet wurden (das heißt, diese Frage hätte zu jedem von ihnen gestellt werden können, nicht zu jedem unbedingt Mr. ).

Sie können mit der folgenden Abfrage und dem Ändern der COLLATEKlausel anhand der Ergebnisse unter der Abfrage sehen, wie die verschiedenen Sensitivitäten in den beiden Versionen von Collations funktionieren:

;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;

Die Anzahl der übereinstimmenden Zeichen bei verschiedenen Sortierungen ist unten angegeben.

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 allen oben aufgelisteten Kollatierungen wird N'' = N'�'ebenfalls true ausgewertet.

AKTUALISIEREN

Ich konnte ein bisschen mehr recherchieren und hier ist, was ich gefunden habe:

Wie es "wahrscheinlich" funktionieren soll

Mit der ICU Collation Demo habe ich das Gebietsschema auf "en-US-u-va-posix" gesetzt, die Stärke auf "primary" gesetzt, "sort keys" (Sortierschlüssel anzeigen) markiert und die folgenden 4 Zeichen eingefügt, die ich aus der kopiert habe Ergebnisse der obigen Abfrage (unter Verwendung der Latin1_General_100_CI_AIKollatierung):

�
Ԩ
ԩ
Ԫ

und das gibt zurück:

Ԫ
    60 2E 02 .
Ԩ
    60 7A .
ԩ
    60 7A .
�
    FF FD .

Überprüfen Sie dann die Zeicheneigenschaften für " " unter http://unicode.org/cldr/utility/character.jsp?a=fffd und stellen Sie FF FDsicher, dass der Sortierschlüssel der Ebene 1 (dh ) mit der Eigenschaft "uca" übereinstimmt. Durch Klicken auf diese Eigenschaft "uca" gelangen Sie zu einer Suchseite - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3DFFFD%3A%5D - mit nur 1 Treffer. In der Datei allkeys.txt wird das Sortiergewicht der Ebene 1 als angezeigt 0F12, und es gibt nur eine Übereinstimmung dafür.

Um sicherzustellen, dass wir das Verhalten richtig interpretieren, habe ich mir ein anderes Zeichen angesehen: GREEK CAPITAL LETTER OMICRON WITH VARIA unter http://unicode.org/cldr/utility/character.jsp?a=1FF8 mit einem "uca" ( dh Level 1 Sortiergewicht / Sortierelement) von 5F30. Wenn Sie auf "5F30" klicken, gelangen Sie zu einer Suchseite - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3D5F30%3A%5D - mit 30 Übereinstimmungen, davon 20 Sie liegen im Bereich von 0 bis 65535 (dh U + 0000 - U + FFFF). Wenn wir in der Datei allkeys.txt nach Code Point 1FF8 suchen , sehen wir ein Sortiergewicht von Stufe 1 12E0. Machen Sie eine "Zählung" in Notepad ++ auf12E0. zeigt 30 Übereinstimmungen an (dies entspricht den Ergebnissen von Unicode.org, obwohl dies nicht garantiert ist, da die Datei für Unicode 5.0 ist und die Site Unicode 9.0-Daten verwendet).

In SQL Server gibt die folgende Abfrage 20 Übereinstimmungen zurück, genau wie die Unicode.org-Suche, wenn die 10 zusätzlichen Zeichen entfernt werden:

;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;

Zur Sicherheit kehren Sie zur Seite ICU Collation Demo zurück und ersetzen Sie die Zeichen im Feld "Eingabe" durch die folgenden 3 Zeichen aus der Liste der 20 Ergebnisse von SQL Server:


𝜪

zeigt, dass sie tatsächlich alle dasselbe 5F 30Sortiergewicht der Ebene 1 haben (passend zum "uca" -Feld auf der Charaktereigenschaftsseite).

SO, scheint es sicher , als ob dieser besondere Charakter sollte nicht etwas anderes anzeigen lassen.

Wie es tatsächlich funktioniert (zumindest in Microsoft-Land)

Im Gegensatz zu SQL Server kann in .NET der Sortierschlüssel für eine Zeichenfolge mithilfe der CompareInfo.GetSortKey-Methode angezeigt werden . Wenn Sie diese Methode verwenden und nur das U + FFFD-Zeichen übergeben, wird der Sortierschlüssel von zurückgegeben 0x0101010100. Durchlaufen Sie dann alle Zeichen im Bereich von 0 bis 65535, um festzustellen, welche einen Sortierschlüssel mit 0x01010101004529 zurückgegebenen Übereinstimmungen hatten. Dies stimmt nicht genau mit dem in SQL Server zurückgegebenen 5840 überein (wenn die Latin1_General_100_CS_AS_WSSortierung verwendet wird), aber es ist ( vorerst) das Beste, was wir bekommen können, wenn ich Windows 10 und .NET Framework Version 4.6.1 verwende, das Unicode v verwendet 6.3.0 gemäß der Tabelle für die CharUnicodeInfo-Klasse(in "Hinweis für Anrufer" im Abschnitt "Bemerkungen"). Im Moment verwende ich eine SQLCLR-Funktion und kann daher die Ziel-Framework-Version nicht ändern. Wenn ich eine Chance bekomme, erstelle ich eine Konsolen-App und verwende eine Ziel-Framework-Version von 4.5, da diese Unicode 5.0 verwendet, das den Kollatierungen der 100er-Reihe entsprechen sollte.

Was dieser Test zeigt, ist, dass selbst ohne die exakt gleiche Anzahl von Übereinstimmungen zwischen .NET und SQL Server für U + FFFD ziemlich klar ist, dass dies kein SQL Server-spezifisches Verhalten ist und ob beabsichtigt oder mit der Implementierung beaufsichtigt von Microsoft stimmt das U + FFFD-Zeichen in der Tat mit einigen Zeichen überein, auch wenn es nicht der Unicode-Spezifikation entsprechen sollte. Und da dieses Zeichen mit U + 0000 (null) übereinstimmt, handelt es sich wahrscheinlich nur um ein Problem mit fehlenden Gewichten.

EBENFALLS

In Bezug auf den Unterschied im Verhalten der =Abfrage gegenüber der LIKE N'%�%'Abfrage hat dies mit den Platzhaltern und den fehlenden (ich nehme an) Gewichten für diese (dh � Ƕ Ƿ Ǹ) Zeichen zu tun . Wenn die LIKEBedingung in "einfach" geändert wird, werden LIKE N'�'die gleichen 3 Zeilen wie die =Bedingung zurückgegeben. Wenn das Problem mit den Platzhaltern nicht auf "fehlende" Gewichte zurückzuführen ist (es wird übrigens kein 0x00Sortierschlüssel zurückgegeben CompareInfo.GetSortKey), kann dies daran liegen, dass diese Zeichen möglicherweise eine Eigenschaft haben, mit der der Sortierschlüssel kontextabhängig variiert (dh umgebende Zeichen) ).

Solomon Rutzky
quelle
Danke - in der verlinkten allkeys.txt sieht es so aus, als gäbe es nichts anderes mit dem gleichen Gewicht wie FFFD (Suche nach *0F12.0020.0002.FFFDnur einem Ergebnis). Aus der Beobachtung von @ Forrest, dass sie alle mit der leeren Zeichenfolge übereinstimmen und etwas mehr über das Thema lesen, geht hervor, dass das Gewicht, das sie in den verschiedenen nicht-binären Kollatierungen teilen, tatsächlich Null ist, glaube ich.
Martin Smith
1
@MartinSmith Hat einige Nachforschungen mit ICU Collation Demo angestellt und � A a \u24D0einige andere, die in der 5839-Übereinstimmungen-Ergebnismenge enthalten waren. Es sieht so aus, als ob Sie das erste Gewicht nicht überspringen können, und dieses Ersatzzeichen ist das einzige, mit dem begonnen wird 0F12. Viele andere hatten auch ein einzigartiges erstes Gewicht und viele fehlten vollständig in der Allkeys-Datei. Dies könnte also ein Implementierungsfehler sein, der auf menschliches Versagen zurückzuführen ist. Ich habe dieses Zeichen in der "nicht unterstützten" Gruppe auf der Unicode-Site in ihren Kollatierungsdiagrammen gesehen. Werde morgen mehr schauen.
Solomon Rutzky
Rextester verwendet 4.5. Ich sehe tatsächlich weniger Übereinstimmungen mit dieser Version (3385). Vielleicht setze ich eine andere Option als Sie? rextester.com/JBWIN31407
Martin Smith
BTW , dass Sortierschlüssel von 01 01 01 01 00hier erwähnt wird archives.miloush.net/michkap/archive/2007/09/10/4847780.html (sieht aus wie CompareInfo.InternalGetSortKeyAnrufe LCMapStringEx)
Martin Smith
@MartinSmith Ich habe ein wenig damit gespielt, bin mir aber nicht sicher, was der Unterschied ist. Das Betriebssystem, auf dem .NET ausgeführt wird, spielt eine Rolle. Wenn ich Zeit habe, werde ich morgen nach mehr suchen. Unabhängig von der Anzahl der Übereinstimmungen scheint dies zumindest den Grund für das Verhalten zu bestätigen, insbesondere jetzt, da wir dank des von Ihnen verlinkten Blogs und einiger anderer darin verlinkter Blogs einen Einblick in die Sortierschlüsselstruktur haben. Die von mir verlinkte Seite CharUnicodeInfo erwähnt die zugrunde liegenden Kollatierungsaufrufe. Dies ist die Grundlage für meinen Vorschlag hier: connect.microsoft.com/SQLServer/feedback/details/2932336 :-)
Solomon Rutzky