Bessere Techniken zum Trimmen führender Nullen in SQL Server?

161

Ich benutze dies seit einiger Zeit:

SUBSTRING(str_col, PATINDEX('%[^0]%', str_col), LEN(str_col))

In letzter Zeit habe ich jedoch ein Problem mit Spalten mit allen "0" -Zeichen wie "00000000" festgestellt, da niemals ein nicht "0" -Zeichen gefunden wird, das übereinstimmt.

Eine alternative Technik, die ich gesehen habe, ist die Verwendung von TRIM:

REPLACE(LTRIM(REPLACE(str_col, '0', ' ')), ' ', '0')

Dies hat ein Problem, wenn eingebettete Leerzeichen vorhanden sind, da diese in "0" umgewandelt werden, wenn die Leerzeichen wieder in "0" umgewandelt werden.

Ich versuche eine skalare UDF zu vermeiden. Ich habe in SQL Server 2005 viele Leistungsprobleme mit UDFs festgestellt.

Cade Roux
quelle
Wird der Rest der Zeichenfolge immer nur 'numerische' Zeichen enthalten, oder haben Sie möglicherweise auch Alphas? Wenn es sich nur um numerische Daten handelt, scheint Quassnois Vorschlag, eine ganze Zahl und zurück zu setzen, gut zu sein.
Robsoft
Es ist eine allgemeine Technik. Dies sind in der Regel Kontonummern, die in einem nicht konformen Feld eingehen, und ich muss sicherstellen, dass sie den Konformationsregeln entsprechen, die das Data Warehouse in seiner ETL verwendet (was natürlich in der SSIS-Umgebung mit viel mehr Funktionen der Fall ist, nehme ich an, dass sie verwendet werden. TrimStart).
Cade Roux

Antworten:

282
SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col))
Arvo
quelle
2
Clever, ich wünschte, ich hätte daran gedacht.
Cade Roux
4
Egal, mir wurde klar, dass das '.' ist nicht in der Teilzeichenfolge, weil es nur zum Finden des Musters verwendet wird - es ist noch cleverer als ich dachte.
Cade Roux
2
Das Einkapseln in eine Funktion führte zu einer Verlangsamung meiner Abfragen. Ich bin mir nicht ganz sicher warum, aber ich denke, es hat mit der Typkonvertierung zu tun. Die Verwendung des SUBSTRING Inline war viel schneller.
Ronnie Overby
1
Die Frage gibt an, dass das Problem darin besteht, dass Sie beim Analysieren einer Null ('0') ein Leerzeichen erhalten. Sie müssen in der Lage sein, den Unterschied zwischen einem '0'-Wert und einem leeren Wert zu erkennen. In meinem Beitrag finden Sie eine vollständige Lösung: stackoverflow.com/a/21805081/555798
MikeTeeVee
1
@Arvo Wow ... Für eine Minute war ich verwirrt und dachte, ich hätte diese Frage beantwortet, die mir helfen würde. Zum ersten Mal habe ich einen anderen Arvoauf SO gesehen!
Arvo Bowen
41

Warum wirfst du den Wert nicht einfach auf INTEGERund dann zurück zu VARCHAR?

SELECT  CAST(CAST('000000000' AS INTEGER) AS VARCHAR)

--------
       0
Quassnoi
quelle
11
Es ist eine Zeichenfolgenspalte, daher würde ich vermuten, dass sie von Zeit zu Zeit nicht numerische Daten erwarten. So etwas wie eine MRN-Nummer, bei der die Daten meist nur numerisch sind.
Joel Coehoorn
1
Leider funktioniert dies nur für numerische Daten, und manchmal überschreiten die Zeichenfolgen auch den Bereich für Ganzzahlen, sodass Sie bigint verwenden müssen.
Cade Roux
3
SELECT CASE ISNUMERIC(str_col) WHEN 1 THEN CAST(CAST(str_col AS BIGINT) AS VARCHAR(255)) ELSE str_col END
Yuriy Rozhovetskiy
Selbst mit BIGINTschlagen einige Arten von Zeichenfolgen diese Konvertierung immer noch fehl. Betrachten Sie 0001E123zum Beispiel.
Roaima
1
Nach meinen Tests (und Erfahrungen) ist dies im Vergleich zur akzeptierten Antwort eine relativ kostspielige Operation. Aus Leistungsgründen ist es am besten, das Ändern von Datentypen oder das Vergleichen von Daten verschiedener Typen zu vermeiden, wenn dies in Ihrer Macht steht.
Reedstonefood
14

Andere Antworten hier nicht zu berücksichtigen, wenn Sie alle Nullen (oder sogar eine einzelne Null) haben.
Einige setzen eine leere Zeichenfolge standardmäßig auf Null, was falsch ist, wenn sie leer bleiben soll.
Lesen Sie die ursprüngliche Frage erneut. Dies beantwortet, was der Fragesteller will.

Lösung 1:

--This example uses both Leading and Trailing zero's.
--Avoid losing those Trailing zero's and converting embedded spaces into more zeros.
--I added a non-whitespace character ("_") to retain trailing zero's after calling Replace().
--Simply remove the RTrim() function call if you want to preserve trailing spaces.
--If you treat zero's and empty-strings as the same thing for your application,
--  then you may skip the Case-Statement entirely and just use CN.CleanNumber .
DECLARE @WackadooNumber VarChar(50) = ' 0 0123ABC D0 '--'000'--
SELECT WN.WackadooNumber, CN.CleanNumber,
       (CASE WHEN WN.WackadooNumber LIKE '%0%' AND CN.CleanNumber = '' THEN '0' ELSE CN.CleanNumber END)[AllowZero]
 FROM (SELECT @WackadooNumber[WackadooNumber]) AS WN
 OUTER APPLY (SELECT RTRIM(RIGHT(WN.WackadooNumber, LEN(LTRIM(REPLACE(WN.WackadooNumber + '_', '0', ' '))) - 1))[CleanNumber]) AS CN
--Result: "123ABC D0"

Lösung Nr. 2 (mit Beispieldaten):

SELECT O.Type, O.Value, Parsed.Value[WrongValue],
       (CASE WHEN CHARINDEX('0', T.Value)  > 0--If there's at least one zero.
              AND LEN(Parsed.Value) = 0--And the trimmed length is zero.
             THEN '0' ELSE Parsed.Value END)[FinalValue],
       (CASE WHEN CHARINDEX('0', T.Value)  > 0--If there's at least one zero.
              AND LEN(Parsed.TrimmedValue) = 0--And the trimmed length is zero.
             THEN '0' ELSE LTRIM(RTRIM(Parsed.TrimmedValue)) END)[FinalTrimmedValue]
  FROM 
  (
    VALUES ('Null', NULL), ('EmptyString', ''),
           ('Zero', '0'), ('Zero', '0000'), ('Zero', '000.000'),
           ('Spaces', '    0   A B C '), ('Number', '000123'),
           ('AlphaNum', '000ABC123'), ('NoZero', 'NoZerosHere')
  ) AS O(Type, Value)--O is for Original.
  CROSS APPLY
  ( --This Step is Optional.  Use if you also want to remove leading spaces.
    SELECT LTRIM(RTRIM(O.Value))[Value]
  ) AS T--T is for Trimmed.
  CROSS APPLY
  ( --From @CadeRoux's Post.
    SELECT SUBSTRING(O.Value, PATINDEX('%[^0]%', O.Value + '.'), LEN(O.Value))[Value],
           SUBSTRING(T.Value, PATINDEX('%[^0]%', T.Value + '.'), LEN(T.Value))[TrimmedValue]
  ) AS Parsed

Ergebnisse:

MikeTeeVee_SQL_Server_Remove_Leading_Zeros

Zusammenfassung:

Sie könnten das, was ich oben habe, für eine einmalige Entfernung von führenden Nullen verwenden.
Wenn Sie vorhaben, es häufig wiederzuverwenden, platzieren Sie es in einer Inline-Table-Valued-Funktion (ITVF).
Ihre Bedenken hinsichtlich Leistungsproblemen mit UDFs sind verständlich.
Dieses Problem gilt jedoch nur für All-Scalar-Funktionen und Multi-Statement-Table-Funktionen.
Die Verwendung von ITVFs ist vollkommen in Ordnung.

Ich habe das gleiche Problem mit unserer Datenbank von Drittanbietern.
Mit alphanumerischen Feldern werden viele ohne die führenden Leerzeichen eingegeben, verdammte Menschen!
Dies macht Verknüpfungen unmöglich, ohne die fehlenden führenden Nullen zu bereinigen.

Fazit:

Anstatt die führenden Nullen zu entfernen, sollten Sie Ihre abgeschnittenen Werte bei Ihren Verknüpfungen nur mit führenden Nullen auffüllen.
Besser noch, bereinigen Sie Ihre Daten in der Tabelle, indem Sie führende Nullen hinzufügen und dann Ihre Indizes neu erstellen.
Ich denke, das wäre viel schneller und weniger komplex.

SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF(' 0A10  ', ''))), 10)--0000000A10
SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF('', ''))), 10)--NULL --When Blank.
MikeTeeVee
quelle
4
@DiegoQueiroz Wenn die Antwort falsch ist, dann rang bitte herunter und erkläre, warum es nicht funktioniert. Wenn die Antwort funktioniert, aber für Sie zu umfassend ist, rangieren Sie mich oder andere Mitglieder auf dieser Website bitte nicht herunter. Danke für den Kommentar. Es ist ein gutes Feedback zu hören - ich sage das aufrichtig.
MikeTeeVee
5

Anstelle eines Leerzeichens ersetzen Sie die Nullen durch ein 'seltenes' Leerzeichen, das normalerweise nicht im Text der Spalte enthalten sein sollte. Ein Zeilenvorschub ist wahrscheinlich gut genug für eine solche Spalte. Dann können Sie normal LTrim und das Sonderzeichen wieder durch Nullen ersetzen.

Joel Coehoorn
quelle
3

Folgendes gibt '0' zurück, wenn die Zeichenfolge vollständig aus Nullen besteht:

CASE WHEN SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col)) = '' THEN '0' ELSE SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col)) END AS str_col
Scott
quelle
Dies gibt auch Null zurück, wenn der Wert keine Nullen hat (leer ist).
MikeTeeVee
warum gibt es str_col + '.' und nicht nur str_col? Was macht der Punkt?
Muflix
2

Das macht eine schöne Funktion ....

DROP FUNCTION [dbo].[FN_StripLeading]
GO
CREATE FUNCTION [dbo].[FN_StripLeading] (@string VarChar(128), @stripChar VarChar(1))
RETURNS VarChar(128)
AS
BEGIN
-- http://stackoverflow.com/questions/662383/better-techniques-for-trimming-leading-zeros-in-sql-server
    DECLARE @retVal VarChar(128),
            @pattern varChar(10)
    SELECT @pattern = '%[^'+@stripChar+']%'
    SELECT @retVal = CASE WHEN SUBSTRING(@string, PATINDEX(@pattern, @string+'.'), LEN(@string)) = '' THEN @stripChar ELSE SUBSTRING(@string, PATINDEX(@pattern, @string+'.'), LEN(@string)) END
    RETURN (@retVal)
END
GO
GRANT EXECUTE ON [dbo].[FN_StripLeading] TO PUBLIC
user2600313
quelle
Dies gibt auch Null zurück, wenn der Wert keine Nullen hat (leer ist). Diese Antwort verwendet auch eine Skalarfunktion mit mehreren Anweisungen, wenn in der obigen Frage ausdrücklich angegeben ist, dass die Verwendung von UDFs vermieden werden soll.
MikeTeeVee
2

cast (Wert als int) funktioniert immer, wenn string eine Zahl ist

Tichra
quelle
Dies gibt keine Antwort auf die Frage. Um einen Autor zu kritisieren oder um Klärung zu bitten, hinterlassen Sie einen Kommentar unter seinem Beitrag. - Von der Überprüfung
Josip Ivic
1
Tatsächlich ist es eine Antwort, weil es funktioniert? Antworten müssen nicht lang sein
Tichra
Sie haben Recht, dass die Antworten nicht langwierig sein müssen, sie sollten jedoch nach Möglichkeit vollständig sein und Ihre Antwort nicht. es ändert den Datentyp des Ergebnisses. Ich glaube, dies wäre eine bessere Antwort gewesen: SELECT CAST (CAST (Wert AS Int) AS VARCHAR). Sie sollten auch erwähnen, dass bei Int eine Fehlermeldung angezeigt wird, wenn der berechnete Wert 2,1 x 10 ^ 9 (achtstelliges Limit) überschreitet. Bei Verwendung von BigInt wird der Fehler angezeigt, wenn der Wert etwa 19 Stellen (9,2 x 10 ^ 18) überschreitet.
J. Chris Compton
2

Meine Version davon ist eine Adaption von Arvos Arbeit, mit etwas mehr, um zwei weitere Fälle zu gewährleisten.

1) Wenn wir alle Nullen haben, sollten wir die Ziffer 0 zurückgeben.

2) Wenn wir ein Leerzeichen haben, sollten wir trotzdem ein Leerzeichen zurückgeben.

CASE 
    WHEN PATINDEX('%[^0]%', str_col + '.') > LEN(str_col) THEN RIGHT(str_col, 1) 
    ELSE SUBSTRING(str_col, PATINDEX('%[^0]%', str_col + '.'), LEN(str_col))
 END
Brisbe
quelle
1
replace(ltrim(replace(Fieldname.TableName, '0', '')), '', '0')

Der Vorschlag von Thomas G arbeitete für unsere Bedürfnisse.

Das Feld in unserem Fall war bereits eine Zeichenfolge und nur die führenden Nullen mussten abgeschnitten werden. Meistens ist alles numerisch, aber manchmal gibt es Buchstaben, sodass die vorherige INT-Konvertierung abstürzt.

Geil
quelle
1
SELECT CAST(CAST('000000000' AS INTEGER) AS VARCHAR)

Dies hat eine Begrenzung für die Länge der Zeichenfolge, die in eine INT konvertiert werden kann

Curt Ehrhart
quelle
Können Sie in Ihrer Antwort etwas näher erläutern, warum dies Ihrer Meinung nach funktionieren wird? Was würde passieren, wenn dies eine Zahl ungleich Null mit einer Reihe führender Nullen wäre?
Taegost
Wenn Ihre Zahlen 18-stellig oder weniger sind (und die meisten 19-stelligen Zahlen funktionieren, weil das Limit tatsächlich 9,2 x 10 ^ 18 beträgt), können Sie SELECT CAST (CAST (@Field_Name AS BigInt) AS VARCHAR) verwenden, um führende Nullen zu entfernen. HINWEIS: Dies schlägt fehl, wenn Sie nicht numerische Zeichen (Bindestrich, Buchstabe, Punkt usw.) mit der Fehlermeldung 8114 "Fehler beim Konvertieren des Datentyps varchar in bigint" haben.
J. Chris Compton
1

Wenn Sie Snowflake SQL verwenden, verwenden Sie möglicherweise Folgendes:

ltrim(str_col,'0')

Die Funktion ltrim entfernt alle Instanzen des angegebenen Zeichensatzes von der linken Seite.

Ltrim (str_col, '0') auf '00000008A' würde also '8A' zurückgeben.

Und rtrim (str_col, '0.') Auf '$ 125.00' würde '$ 125' zurückgeben.

JJFord3
quelle
1
  SUBSTRING(str_col, IIF(LEN(str_col) > 0, PATINDEX('%[^0]%', LEFT(str_col, LEN(str_col) - 1) + '.'), 0), LEN(str_col))

Funktioniert auch mit '0', '00' und so weiter.

Lisandro
quelle
0

Versuche dies:

replace(ltrim(replace(@str, '0', ' ')), ' ', '0')
Shetty
quelle
0

Wenn Sie nicht in int konvertieren möchten, bevorzuge ich die folgende Logik, da sie Nullen verarbeiten kann. IFNULL (Feld, LTRIM (Feld, '0'))

Stoßwelle
quelle
0

In MySQL können Sie dies tun ...

Trim(Leading '0' from your_column)
joe_evans
quelle