Passende linke und rechte einfache Anführungszeichen als Apostophes

7

Ich habe vier Spalten mit Namen und möchte diese mit einer LIKEin einer Microsoft SQL Server-Umgebung durchsuchen .

Die Komplikation kommt , dass Namen kann links und rechts Apostrophe / gewinkelt Apostrophe (dh umfassen und , char(145)und char(146)jeweils), die einen geraden Apostroph übereinstimmen sollte (dh ', char(39))

Folgendes zu tun ist sehr langsam:

SELECT person_id
FROM person
WHERE REPLACE(
          REPLACE(
              person_name, 
              CHAR(145), 
              CHAR(39)
          ), 
          CHAR(146), 
          CHAR(39)
      ) LIKE '{USER_INPUT}'

Wie in der SQL-Ersetzungsanweisung erläutert , REPLACEdie beim Stapelüberlauf zu langsam ist , liegt dies daran, dass die Verwendung von die Anweisung nicht aufladbar macht.

Gibt es eine Möglichkeit, wie SQL Server mit solchen Situationen besser umgehen kann?

Eine Lösung , die vorgeschlagen wurde , ist die Anwendung zu haben , erzeugt einen ‚suchbar‘ Wert, der alle Felder verkettet ( person_name, person_surname, person_nickname, etc.) und wandelt die problematischen Zeichen an der Stelle der Bearbeitung. Dies könnte effektiv indiziert und durchsucht werden. Das Speichern dieser Daten in einer separaten SQL-Tabelle / -Spalte würde weniger Umschreiben der Anwendung erfordern als das Implementieren einer vollständigen NoSQL-Lösung wie Lucene.

Das obige Beispiel ist eine Vereinfachung: Die Abfrage wird nicht wie oben erläutert buchstäblich erstellt, und wir implementieren SQL-Injection-Schutz (und andere).

Die Frage ist, wie die abgewinkelten Apostrophe in den Tabellendaten durch gerade ersetzt werden können. Zu klären:

  • Benutzerbedarf O‘Malley- dies sollte mit beiden O‘Malleyoder übereinstimmenO'Malley
  • Benutzerbedarf O'Malley- dies sollte mit beiden O‘Malleyoder übereinstimmenO'Malley

Wir müssen die SQL-Daten ersetzen, nicht die Benutzereingaben. Wir können die Benutzereingaben auf dem Weg durch die Anwendung so konvertieren, dass wir sie in einfache Apostrophe ändern, wenn sie abgewinkelte Apostrophe eingeben, bevor wir sie an SQL übergeben. Es sind die Daten in SQL, die wir standardisieren müssen.

Leider müssen die Daten als korrekte abgewinkelte Klammer in der Datenbank verbleiben, aber wenn wir die Suche durchführen, müssen wir sie alle mit geraden Apostrophen vergleichen.

JLo
quelle

Antworten:

7

Der beste Weg, um Ihr Problem zu behandeln (und SQL-Injection zu vermeiden), besteht darin, Ihre Benutzereingaben als Variable zu übergeben. Da Sie ein verwenden LIKE, können Sie Folgendes tun:

CREATE TABLE #person (person_name nvarchar(50))
INSERT INTO #person VALUES (N'Bob'),(N'Bo''b'),(N'Bo‘b'),(N'Bo’b'),(N'Bo#b'),(N'Bo^b')

DECLARE @user_input nvarchar(50) = 'Bo’b'

SET @user_input = REPLACE(
                        REPLACE(
                                REPLACE(@user_input, N'‘', N''''), 
                                N'’', N''''), 
                                N'''', N'[‘’'']')

-- @user_input now == Bo[‘’']b

SELECT person_name
FROM #person
WHERE person_name LIKE @user_input

Grundsätzlich ersetzt dies alle verschiedenen durch einen einzigen Typ (das ') und setzt dann [] um alle drei, so dass sie in der verwendet werden LIKE.

Kenneth Fisher
quelle
Hallo Kenneth, danke für die Antwort, aber die Benutzereingaben, die wir auf dem Weg durch die Anwendung standardisieren können, sind die SQL-Daten, die wir vergleichen möchten. Wir können also alle Benutzerapostrophen "begradigen", bevor wir sie an den SQL-Befehl übergeben. Wir möchten dies jedoch mit jeder Art von Apostroph in den Werten von Person_Name abgleichen.
JLo
1
Ich korrigiere nicht nur die Benutzereingaben. Ich stelle einen ähnlichen Vergleich auf. Wenn Sie sich die Variable @user_inputdirekt vor der tatsächlichen ansehen, SELECTsehen Sie die Zeichen zwischen den []'s. Das bedeutet, dass dieser bestimmte Punkt mit einem der Zeichen zwischen ihnen übereinstimmt.
Kenneth Fisher
1
@JLo Nochmals, was Sie verlangen, ist genau das , was Kenneths Lösung hier tut: Er konvertiert alle drei Apostrophe in einen mehrstelligen Platzhalter - [‘’']- für den LIKEOperator. Daher spielt es keine Rolle, was der Benutzer übergibt, selbst wenn er mehr als einen Typ in einem Suchbegriff verwendet, da jeder mit einer der drei in den Daten übereinstimmt.
Solomon Rutzky
Vielen Dank! Ich habe das komplett vermisst, das funktioniert perfekt
JLo
2

Ich hätte schwören können, dass ich gesehen habe, wie diese Zeichen irgendwo gleichgesetzt wurden, aber jetzt kann ich sie nicht finden. Ich habe alle Kollatierungen in SQL Server 2012 und 2014 überprüft, und keine davon entspricht CHAR(39)einer der beiden anderen. Vergessen Sie also diese ursprüngliche Idee.

Eine Option, wenn die genaue Art des Apostrophs nicht von besonderer Bedeutung ist, besteht darin, nur die Daten zu aktualisieren:

UPDATE person 
SET person_name = REPLACE(...)

... umwandeln CHAR(145)und CHAR(146)in CHAR(39). Dann müssen Sie nichts programmgesteuert tun. Sie müssen nur gelegentlich die Daten überprüfen oder einen Auslöser erstellen, um diese in CHAR(39)on INSERToder zu übersetzen UPDATE.

Solomon Rutzky
quelle
1
@JLo Ich verstehe nicht, warum Sie eine Kopie der Daten speichern müssten. Kenneths vorgeschlagene Lösung behandelt alle 3 in den vorhandenen Daten. Weitere Informationen finden Sie in meinem Antwortkommentar zu seiner Antwort.
Solomon Rutzky
0

Seien Sie vorsichtig: Sind Sie sicher, dass Sie dort wirklich Unicode haben und keine 8-Bit-Codepage? Während 145 in vielen Schriftarten als ziemlich angezeigt wird, ist dies im Unicode-Standard nicht offiziell (145 und 146 sind als PRIVATE USE ONE bzw. PRIVATE USE TWO aufgeführt: http://www.fileformat.info/info/unicode/). char / 0091 / index.htm ). Wenn Sie 8-Bit-Text haben, kann dieses Zeichen zu etwas anderem in einer anderen Codepage werden (145 ist in einigen Fällen æ), sodass Sie möglicherweise Probleme mit der Zeichenübersetzung in Ihrer Anwendung haben. Geschweifte einfache Anführungszeichen sind 2018 und 2019 in Unicode.

Abgesehen von solchen möglichen Codierungsproblemen: Wenn Sie eine Funktion um die gesuchte Spalte wickeln, wird der Filter nicht aufladbar, wie Sie bereits festgestellt haben. In diesem Fall ist drei sehr wenig, was Sie ohne strukturelle Änderungen dagegen tun können.

Wenn Sie einen Index haben person_name, können Sie hoffentlich einen Tabellenscan in einen Indexscan umwandeln (obwohl dies in Abhängigkeit von anderen Filtern, einschließlich der durch Joins usw. implizierten, in der Abfrage möglicherweise nicht ganz so einfach ist). .

Wenn Sie die Möglichkeit haben, das Tabellenlayout sowie den Abfragecode zu steuern, können Sie eine persistierte berechnete Spalte erstellen, die eine "kanonische" Zeichenfolge enthält (in diesem Fall die Eine kanonische Darstellung würde durch so etwas wie person_name_canon = REPLACE(REPLACE(person_name, CHAR(145), CHAR(39)), CHAR(146), CHAR(39)))) erzeugt werden. Stellen Sie sicher, dass die Spalte an relevanten Indizes beteiligt ist, und wenn Sie nach dieser Spalte suchen, können Sie dies tun WHERE person_name_canon LIKE REPLACE(REPLACE(@user_input, CHAR(145), CHAR(39)), CHAR(146), CHAR(39))).

Wenn Sie die berechnete Spalte nicht hinzufügen können, aber ein nützlicher Index für person_nameeinen leicht hackigen Ansatz vorhanden ist, können Sie Folgendes tun:

WHERE REPLACE(REPLACE(person_name, CHAR(145), CHAR(39)), CHAR(146), CHAR(39))) LIKE @user_input
AND   person_name LIKE CASE
                       WHEN @user_input LIKE '%''%'
                       THEN SUBSTIRNG(@user_input,1,CHARINDEX('''',@user_input)-1)
                       ELSE @user_input
                       END
                   + '%'

Bitte beachten Sie, dass ich dies überhaupt nicht getestet habe, damit es möglicherweise nicht den gewünschten Effekt hat, aber theoretisch sollte die zweite Klausel zu einem sargable Teilfilter führen. Wenn also @user_input ist Michael O'Brien, kann der Index verwendet werden, um Text zu finden, der mit Michael Odem anderen beginnt Der Filter wirft diejenigen aus, die nicht vollständig übereinstimmen, nachdem alle Ersetzungen der gespeicherten Daten abgeschlossen sind.

Möglicherweise müssen Sie auch die verschiedenen Anführungszeichen in der Eingabe berücksichtigen, wenn dies in Ihrer Geschäftslogik vor der Suche nicht normalisiert wird.

WHERE REPLACE(REPLACE(person_name, CHAR(145), CHAR(39)), CHAR(146), CHAR(39))) LIKE @user_input
AND   person_name LIKE CASE
                       WHEN @user_input LIKE '%''%'            THEN SUBSTIRNG(@user_input,1,CHARINDEX('''',@user_input)-1)
                       WHEN @user_input LIKE '%'+CHAR(145)+'%' THEN SUBSTIRNG(@user_input,1,CHARINDEX(CHAR(145),@user_input)-1)
                       WHEN @user_input LIKE '%'+CHAR(146)+'%' THEN SUBSTIRNG(@user_input,1,CHARINDEX(CHAR(145),@user_input)-1)
                       ELSE @user_input
                       END
                   + '%'
David Spillett
quelle
1
Hallo David. 1) Wir können davon ausgehen, dass das OP 8-Bit-Daten verwendet, da das OP CHAR(145)nicht verwendet NCHAR(145)und die Frage als derzeit nicht potenziell langsam eingestuft wird. 2) Auch wenn die tatsächlichen Daten NVARCHAR sind, CHAR(145)funktioniert die Verwendung so, wie sie implizit in konvertiert wird NCHAR(8216). 3) Eine persistierte berechnete Spalte ist eine völlig unnötige Verschwendung von Speicherplatz sowohl auf der Festplatte als auch im Speicher, da Sie mit einem LIKEverwendeten Platzhalter eine Indexsuche durchführen können, solange das Muster nicht mit einem beginnt . 4) Es gibt keinen Grund, einen der beiden Hacks durchzuführen, da Kenneths Antwort alle Fälle behandelt.
Solomon Rutzky
@srutzky: 1) genau, so dass die verwendete Codepage zu einem Problem wird, könnten die geschweiften Anführungszeichen irgendwo anders und 145/146 landen (obwohl sie höchstwahrscheinlich dort sind oder bereits in 39 übersetzt wurden, aber es lohnt sich, darüber nachzudenken internationale Apps), 3) Der Index funktioniert nicht, egal wo sich die Platzhalter befinden (n't), wenn die Funktionen auf die Spalte angewendet werden, wie sie im Beispiel ( WHERE FUNCTION({column}) = {input}) nicht auf der anderen Seite des Vergleichs stehen
David Spillett
Mehr @srutzky: ... Aber ja: 4) Die Übersetzung der Benutzereingaben in der Art, wie Kenith es tut (seine Antwort war nicht da, als ich anfing, meine einzugeben), ist im Fall einer einzelnen Eingabe definitiv viel sauberer, ich war überkompliziert! Der Hack kann eine gewisse Relevanz haben, wenn versucht wird, sich anzuschließen, anstatt nach einem Wert zu filtern. Ich werde nachdenken, wenn ich nicht bei der Arbeit bin, und meine Antwort entweder umformulieren oder entfernen.
David Spillett