it-swarm.com.de

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

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

Rückgabe

Col
A
B
C
Ƕ
Ƿ
Ǹ

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

Kehrt zurück

Col
Ƕ
Ƿ
Ǹ

Das Generieren jedes möglichen Doppelbyte- "Zeichens" mit den folgenden Angaben zeigt, dass die Version = Mit 21.229 und die Version LIKE N'%�%' Mit allen übereinstimmt (ich habe einige nicht-binäre Kollatierungen mit derselben versucht Ergebnis).

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 Licht ins Dunkel bringen, was hier vor sich geht?

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

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

Das Ändern von SELECT in der letzten Abfrage in SELECT I, N, RANK() OVER(ORDER BY N) zeigt, dass SQL Server die Zeichen nicht einordnen kann. Es scheint, dass jedes Zeichen, das nicht von der Sortierung behandelt wird, als gleichwertig angesehen wird.

Eine Datenbank mit einer Latin1_General_100_CS_AS - Kollatierung erzeugt 5840 Übereinstimmungen. Latin1_General_100_CS_AS Reduziert die = - Übereinstimmungen erheblich, ändert aber nichts am Verhalten von LIKE. 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 der Suche mit Platzhaltern LIKE ignoriert werden.

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

Alle diese Zeichen verhalten sich wie die leere Zeichenfolge für LIKE und =. Sie bewerten sogar als gleichwertig. N'' = N'�' Ist wahr, und Sie können es in einem LIKE Vergleich einzelner Leerzeichen LIKE '_' + nchar(65533) + '_' ohne Auswirkung ablegen. LEN Vergleiche führen jedoch zu unterschiedlichen Ergebnissen, sodass es sich wahrscheinlich nur um bestimmte Zeichenfolgenfunktionen handelt.

Ich denke, das LIKE Verhalten ist für diesen Fall korrekt; 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 Zeichen)

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

Ich denke, dies ist die Gruppe von "nicht gewichteten Zeichen", die in der Dokumentation erwähnt wird, Collation and Unicode Support .

24
Martin Smith

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 nicode -Spezifikation dargestellten Sprachen zu finden sind. Dieses System gilt für nicht-binäre Kollatierungen für alle NVARCHAR -Daten und für VARCHAR -Daten, die eine Windows-Kollatierung und keine SQL Server-Kollatierung verwenden (eine beginnend mit SQL_). . Dieses System gilt nicht für VARCHAR -Daten, die eine SQL Server-Sortierung verwenden, da diese einfache Zuordnungen verwenden.

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

  1. Die Standardbestellung/das Standardgewicht in der Datei allkeys.txt (Unten angegeben)
  2. Welche Empfindlichkeiten und Optionen werden verwendet (z. B. wird zwischen Groß- und Kleinschreibung unterschieden oder nicht? Und wenn dies der Fall ist, wird zuerst Groß- oder Kleinschreibung angezeigt?)
  3. Alle auf dem Gebietsschema basierenden Überschreibungen.
  4. Die Version des Unicode-Standards wird verwendet.
  5. Der "menschliche" Faktor (d. H. Unicode ist eine Spezifikation, keine Software und wird daher jedem Anbieter überlassen, um 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 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 Kollatierungsserie 100 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 definiert als:

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

Diese Notation ist in Abschnitt 9.1 Allkeys File Format 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 Codepunkt, den wir betrachten, eine Spezifikation hat, die tatsächlich mit "*" beginnt. In Abschnitt .6 Variable Weighting sind vier mögliche Verhaltensweisen definiert, die auf Kollatierungskonfigurationswerten basieren, auf die wir keinen direkten Zugriff haben (diese sind in der Microsoft-Implementierung jeder Kollatierung fest codiert, z. sensitive verwendet zuerst Kleinbuchstaben oder zuerst Großbuchstaben, eine Eigenschaft, die sich zwischen VARCHAR -Daten unter Verwendung von SQL_ (Kollatierungen und allen anderen Variationen) unterscheidet.

Ich habe keine Zeit, um vollständig zu untersuchen, welche Wege eingeschlagen werden, und um zu schließen, welche Optionen verwendet werden, damit ein soliderer Beweis erbracht werden kann, aber es ist sicher zu sagen, dass in jeder Code Point-Spezifikation, ob etwas oder nicht wird als "gleich" betrachtet, 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 "[*" zählen, werden 4049 Übereinstimmungen zurückgegeben. Wenn Sie ein RegEx "Find"/"Count" mit einem Muster von \[\*\d{4}\.0020\.0002 Durchführen, werden 832 Übereinstimmungen zurückgegeben. Irgendwo in dieser Kombination und möglicherweise einigen anderen Regeln, die ich nicht sehe, sowie einigen Microsoft-spezifischen Implementierungsdetails ist die vollständige Erklärung dieses Verhaltens. Und um klar zu sein, das Verhalten ist für alle übereinstimmenden Zeichen gleich, da sie alle miteinander übereinstimmen, da sie alle das gleiche Gewicht haben, sobald die Regeln angewendet werden (was bedeutet, dass diese Frage zu einem von ihnen gestellt worden sein könnte, nicht zu einem von ihnen unbedingt Herr ).

Mit der folgenden Abfrage können Sie sehen und die COLLATE -Klausel gemäß den Ergebnissen unter der Abfrage ändern, wie die verschiedenen Empfindlichkeiten in den beiden Versionen von Kollatierungen 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 verschiedenen Anzahlen übereinstimmender Zeichen bei verschiedenen Kollatierungen sind unten aufgeführt.

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 aufgeführten Kollatierungen wird N'' = N'�' Ebenfalls als wahr ausgewertet.

AKTUALISIEREN

Ich konnte etwas mehr recherchieren und habe Folgendes gefunden:

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 "primär" gesetzt, "Sortierschlüssel" aktiviert und im Folgenden eingefügt 4 Zeichen, die ich aus den Ergebnissen der obigen Abfrage kopiert habe (unter Verwendung der Latin1_General_100_CI_AI - Kollatierung):

�
Ԩ
ԩ
Ԫ

und das kehrt 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 sicher, dass der Sortierschlüssel der Ebene 1 (dh FF FD) Entspricht der Eigenschaft "uca". Wenn Sie auf diese "uca" -Eigenschaft klicken, gelangen Sie zu einer Suchseite - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%3Auca%3DFFFD%3A%5D = - zeigt nur 1 Übereinstimmung. In der Datei allkeys.txt wird das Sortiergewicht der Ebene 1 als 0F12 Angezeigt, und es gibt nur 1 Ü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 Sortiergewicht/Sortierelement der Stufe 1) 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 - 30 Übereinstimmungen anzeigen, von denen 20 im Bereich von 0 bis 65535 liegen (dh U + 0000 - U + FFFF). Wenn wir in der Datei allkeys.txt nach Code Point 1FF8 suchen, sehen wir ein Sortiergewicht der Stufe 1 von 12E0. Wenn Sie in Notepad ++ unter 12E0. Eine "Zählung" durchführen, werden 30 Übereinstimmungen angezeigt (dies entspricht den Ergebnissen von Unicode.org, obwohl dies nicht garantiert ist, da die Datei für Unicode v 5.0 ist und die Site Unicode v 9.0-Daten verwendet ).

In SQL Server gibt die folgende Abfrage 20 Übereinstimmungen zurück, genau wie bei der Suche nach Unicode.org, 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;

Gehen Sie zur Sicherheit zurück zur Seite ICU Collation Demo) 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 das gleiche Sortiergewicht der Stufe 1 5F 30 haben (passend zum Feld "uca" auf der Zeicheneigenschaftsseite).

Also, es scheint sicher so, als ob dieses bestimmte Zeichen nicht mit irgendetwas anderem übereinstimmen sollte.

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

Im Gegensatz zu SQL Server kann .NET den Sortierschlüssel für eine Zeichenfolge über die Methode CompareInfo.GetSortKey anzeigen. Wenn Sie diese Methode verwenden und nur das Zeichen U + FFFD übergeben, wird der Sortierschlüssel 0x0101010100 Zurückgegeben. Wenn Sie dann alle Zeichen im Bereich von 0 bis 65535 durchlaufen, um festzustellen, welche Zeichen den Sortierschlüssel 0x0101010100 Hatten, wurden 4529 Übereinstimmungen zurückgegeben. Dies stimmt nicht genau mit dem in SQL Server zurückgegebenen 5840 überein (bei Verwendung der Latin1_General_100_CS_AS_WS - Sortierung), aber es ist das Beste, was wir (vorerst) erreichen können, da ich Windows 10 und .NET Framework Version 4.6 verwende. 1, die Unicode v 6.3.0 gemäß der Tabelle für die CharUnicodeInfo-Klasse verwendet (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 die Gelegenheit dazu bekomme, werde ich eine Konsolen-App erstellen und eine Ziel-Framework-Version von 4.5 verwenden, da diese Unicode 5.0 verwendet, das mit den Kollatierungen der Serie 100 übereinstimmen sollte.

Dieser Test zeigt, dass auch ohne genau die gleiche Anzahl von Übereinstimmungen zwischen .NET und SQL Server für U + FFFD ziemlich klar ist, dass dies nicht SQL Server-spezifisches Verhalten ist, und Unabhängig davon, ob die Implementierung von Microsoft beabsichtigt oder übersehen wurde, stimmt das U + FFFD-Zeichen tatsächlich 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 fehlende Gewichte.

[~ # ~] auch [~ # ~]

Der Unterschied im Verhalten der Abfrage = Gegenüber der Abfrage LIKE N'%�%' Hat mit den Platzhaltern und den fehlenden (ich nehme an) Gewichten für diese (dh � Ƕ Ƿ Ǹ) Zu tun. Figuren. Wenn die Bedingung LIKE in einfach LIKE N'�' Geändert wird, werden dieselben 3 Zeilen wie in der Bedingung = Zurückgegeben. Wenn das Problem mit den Platzhaltern nicht auf "fehlende" Gewichte zurückzuführen ist (es wird übrigens kein 0x00 Sortierschlüssel von CompareInfo.GetSortKey Zurückgegeben), kann dies daran liegen, dass diese Zeichen möglicherweise eine Eigenschaft haben, die Ermöglicht, dass der Sortierschlüssel je nach Kontext variiert (dh umgebende Zeichen).

12
Solomon Rutzky