Ersetzen Sie Sonderzeichen in einer Spalte durch Leerzeichen


10

Ich versuche eine Abfrage zu schreiben, die die Sonderzeichen durch Leerzeichen ersetzt. Der folgende Code hilft bei der Identifizierung der Zeilen. (alphanumerische Zeichen, Komma und Leerzeichen sind gültig):

SELECT columnA
FROM tableA
WHERE columnA like '%[^a-Z0-9, ]%'

Wie kann ich die Ersetzungsfunktion in die select-Anweisung integrieren, sodass alle Zeichen außer alphanumerisch, Komma und Leerzeichen in der Ergebnismenge durch '' (Leerzeichen) ersetzt werden? Dieser wird nicht funktionieren:

SELECT replace(columnA,'%[^a-Z0-9, ]%',' ')
FROM tableA
WHERE columnA like '%[^a-Z0-9, ]%'

2
REPLACE kann nicht nach Platzhaltern oder Mustern suchen. Sie müssen die Ersetzungsaufrufe verschachteln, also REPLACE (REPLACE (Spalte A, '$', ''), '&', '') usw. Oder, wenn dies eine einmalige Bereinigung ist, verwenden Sie eine Funktion, die Durchläuft die Zeichen in jedem Wert und fügt ein Leerzeichen hinzu, wenn auf Zeichen gestoßen wird, die nicht im gültigen Bereich liegen.
Aaron Bertrand

2
Obwohl eine UDF funktioniert, kann dies für die Leistung problematisch sein. Ist das eine einmalige Operation? Oder hoffen Sie, dies regelmäßig durchzuführen? Wie viele Zeilen in der Tabelle? Wie groß ist columnA?
Max Vernon

Antworten:


11

Wenn Sie garantiert immer nur die 26 Buchstaben des englischen US-Alphabets (sowohl Groß- als auch Kleinbuchstaben) verwenden, können Sie sicher mit der Verwendung LIKEund / oder PATINDEXder einfachen Bereichsnotation von [a-z](Sie würden es nicht tun) davonkommen müssen ein Großbuchstaben "Z" verwenden, wenn eine Sortierung ohne Berücksichtigung der Groß- / Kleinschreibung verwendet wird).

Wenn Sie jedoch möglicherweise Zeichen erhalten, die noch nicht im US-Alphabet enthalten sind und noch in verschiedenen Codepages / Kollatierungen für VARCHARDaten verfügbar sind (z. B. Þ= lateinisches Großbuchstaben "Thorn" = SELECT CHAR(0xDE)), müssen Sie diese möglicherweise in die Zeichenklasse aufnehmen : [a-z0-9, Þ]. Diese zusätzlichen Zeichen werden natürlich pro Codepage verwendet.

Beachten Sie außerdem, dass sowohl die Einstellungen für den Sortierungstyp (SQL Server vs Windows) als auch die Empfindlichkeitseinstellungen (Groß- und Kleinschreibung, Akzent usw., sensitiv oder unempfindlich) Einfluss darauf haben, welche Zeichen in einem bestimmten Bereich enthalten sind. Beispielsweise sortieren die SQL Server-Kollatierungen Groß- und Kleinbuchstaben in der entgegengesetzten Reihenfolge wie die Windows-Kollatierungen. Das heißt, unter der Annahme einer Sortierung, bei der zwischen Groß- und Kleinschreibung unterschieden wird, reicht eine AaBb...und die andere aus aAbB.... Der Effekt wird sein, dass er für einen von ihnen aim Bereich von liegt, A-Zfür den anderen jedoch nicht. Und der Bereich von a-Zstimmt nicht mit Zeichen in einer binären Kollatierung überein (eines endet entweder mit _BINoder _BIN2, wird aber nicht verwendet _BIN), vorausgesetzt, der Wert von Aist 65 undaist 97, daher ist es ein ungültiger Bereich von 97 bis 65 ;-). Es gibt viel zu viele Variationen, um hier Beispiele zu nennen, daher werde ich versuchen, bald eine ausführliche Erklärung in meinem Blog zu veröffentlichen (und diese dann mit dem Link dazu aktualisieren). Wenn Sie jedoch strengstens nur US-englische Zeichen akzeptieren (auch wenn Sie möglicherweise gültige Buchstaben aus anderen Sprachen erhalten), ist es wahrscheinlich die beste Option, das folgende Muster und die folgende Sortierung zu verwenden:

LIKE '%[^A-Za-z0-9, ]%' COLLATE Latin1_General_100_BIN2

Wenn Sie NVARCHARDaten unterstützen und "Wort" -Zeichen aus verschiedenen Sprachen erhalten können, ist T-SQL keine große Hilfe, da es keine wirkliche Möglichkeit gibt, diese Dinge zu unterscheiden. In diesem Fall sollten Sie einen regulären Ausdruck (RegEx) verwenden - insbesondere die ReplaceMethode / Funktion - und diese sind nur über SQLCLR verfügbar. Das Folgende zeigt ein Beispiel für das Ersetzen mehrerer "Sonderzeichen", wobei jedoch alle gültigen Buchstaben in mindestens einer Sprache verbleiben:

DECLARE @Test NVARCHAR(500);
SET @Test = N'this$is%a<>TEST,;to}⌡↕strip╞╟╚══¶out_ç_ƒ▀ special-ij-೫-chars-舛-დ-א-B';
SELECT SQL#.RegEx_Replace4k(@Test, N'[\W\p{Pc}-[,]]', N' ', -1, 1, NULL); 

Kehrt zurück:

this is a  TEST, to   strip      out ç ƒ  special ij ೫ chars 舛 დ א B

Der RegEx-Ausdruck bedeutet:

  • \W= ein RegEx "Escape", was "ein beliebiges Nicht- Wort-Zeichen" bedeutet
  • \p{Pc}= eine Unicode "Kategorie" von "Interpunktion, Konnektor" (dies wird nur für die Übereinstimmung benötigt, da diese "Kategorie" durch das \WEscape ausdrücklich ausgeschlossen wird )
  • -[,]= Klassensubtraktion (dies wird benötigt, um Kommas vom Abgleich als "speziell" auszuschließen, da sie im \WEscape enthalten sind)

Sie können eine Tabelle einfach aktualisieren, indem Sie Folgendes ausgeben:

UPDATE tbl
SET    tbl.field = SQL#.RegEx_Replace4k(tbl.field, N'[\W\p{Pc}-[,]]', N' ', -1, 1, NULL)
FROM   tbl
WHERE  SQL#.RegEx_IsMatch4k(tbl.field, N'[\W\p{Pc}-[,]]', 1, NULL) = 1;

Bitte beachten Sie, dass ich für diese Beispiele zwei Funktionen verwendet habe, die in der kostenlosen SQL # -Bibliothek der SQLCLR-Funktionen verfügbar sind, die ich erstellt habe (aber auch diese sind kostenlos). Beachten Sie auch, dass ich die "4k" -Versionen verwendet habe, die aufgrund der Verwendung NVARCHAR(4000)anstelle von NVARCHAR(MAX)Parametertypen schneller sind . Wenn Ihre Daten verwendet werden NVARCHAR(MAX), entfernen Sie einfach die "4k" aus den Funktionsnamen.

Bitte beachten Sie auch:


5

Ich habe hier einen Beitrag , der etwas Ähnliches macht .

Grundsätzlich verwende ich einen rekursiven CTE, um immer wieder eine Schleife zu durchlaufen und jeweils ein "schlechtes" Zeichen zu ersetzen. Ich verwende STUFF, um 1 Zeichen zu entfernen (obwohl Sie es verwenden können, um es durch ein Leerzeichen zu ersetzen) und PATINDEX, um die Position des Zeichens zu finden, das ich entfernen möchte. Sie können es leicht ändern, um das zu tun, wonach Sie suchen. Es wird jedoch eine "gute" Liste erstellt, die vorhandene Liste wird jedoch nicht aktualisiert.

DECLARE @Pattern varchar(50) = '%[^A-Za-z0-9, ]%';

WITH FixBadChars AS (SELECT StringToFix, StringToFix AS FixedString, 1 AS MyCounter, Id
                FROM BadStringList
                UNION ALL
                SELECT StringToFix, Stuff(FixedString, PatIndex(@Pattern, 
                    FixedString COLLATE Latin1_General_BIN2), 1, ' ') AS FixedString, 
                    MyCounter + 1, Id
                FROM FixBadChars
                WHERE FixedString COLLATE Latin1_General_BIN2 LIKE @Pattern)
SELECT StringToFix, FixedString, MyCounter, Id
FROM FixBadChars
WHERE MyCounter = 
        (SELECT MAX(MyCounter) 
        FROM FixBadChars Fixed
        WHERE Fixed.Id = FixBadChars.Id)
OPTION (MAXRECURSION 1000);

Sie sollten in der Lage sein, den unteren Teil zu ändern, um ein Update und nicht nur eine Abfrage durchzuführen, aber ich habe es nicht wirklich versucht. Ich bin mir ziemlich sicher, dass es ungefähr so ​​aussehen würde:

UPDATE FixBadChars
SET StringToFix = FixedString
WHERE MyCounter = 
        (SELECT MAX(MyCounter) 
        FROM FixBadChars Fixed
        WHERE Fixed.Id = FixBadChars.Id)
OPTION (MAXRECURSION 1000);

In Bezug auf die Skalierbarkeit habe ich in weniger als 30 Sekunden ~ 170.000 gereinigte Zeilen zurückgegeben. Wieder nicht sicher, ob ich ein Update durchführen soll, aber dies war auf meinem Laptop, der mit nur 6 GB RAM ziemlich langsam ist.


Gut, dass Sie _BIN2anstelle von verwenden _BIN, aber zwei Hinweise: 1) Ich empfehle die Verwendung der neueren Version, die ist Latin1_General_100_BIN2. Noch wichtiger ist jedoch, 2) dass Sie durch Angabe einer binären Kollatierung versehentlich alle Buchstaben (beide Fälle) ausgeschlossen haben, da Großbuchstaben an erster Stelle stehen. Das heißt, der Bereich "gut" beginnt mit [97-90] in Bezug auf dezimale ASCII-Werte, was nichts entspricht. Um eine binäre Kollatierung zu verwenden, müssen Sie seinen ursprünglichen Ausdruck wie folgt ändern : [A-Za-z0-9, ]. Dies schließt jedoch auch gültige Latin1-Zeichen mit Akzenten aus.
Solomon Rutzky

@ Srutzky Interessant. Ich hatte nicht daran gedacht, beide Bereiche anzugeben A-Za-z. Ich hätte es wahrscheinlich tun sollen. Obwohl ich sagen werde, dass es in meinen Tests richtig funktioniert hat (folgen Sie dem Link am Anfang).
Kenneth Fisher

Es hat bei Ihnen funktioniert, weil Ihr Muster in Ihrem Beitrag korrekt angegeben ist. Es ist nur das Kopieren / Einfügen des ursprünglichen Musters des OP in Ihren Code, das hier das Problem darstellt. Und das liegt nur daran, dass das ursprüngliche Muster des OP für eine Suche ohne Berücksichtigung der Groß- und Kleinschreibung in Ordnung ist. Wenn Sie den Test, den Sie im letzten März mit dem hier gezeigten Muster durchgeführt haben, erneut ausführen, sollten nur die Zahlen, das Komma und das Leerzeichen übrig bleiben ;-).
Solomon Rutzky

1
@ Srutzky Duh. Ehrlich gesagt vergessen, ich hatte ihr Muster kopiert :)
Kenneth Fisher

0
Declare @String nchar(2000)='hg$%^AB,.:23ab-=+'

Declare @NewString VARCHAR(2000)=''
Declare @Lenght int=LEN(@String)
Declare @Index int=1

WHILE (@Index <= @Lenght)
BEGIN
    Declare @Letter nchar(1)=Substring(@String,@Index,1);
    Declare @ASCII int=ASCII(@Letter);
    If((@ASCII >= 48 and @ASCII <= 57) or (@ASCII >= 97 and @ASCII <= 122) or (@ASCII >= 65 and @ASCII <= 90))
    BEGIN
        SET @NewString += @Letter
    END
    ELSE
    BEGIN
        SET @NewString += ' '
    END
    SET @Index+=1

END
Select @NewString
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.