Lepsze techniki przycinania zer wiodących w programie SQL Server?

161

Używam tego od jakiegoś czasu:

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

Jednak ostatnio napotkałem problem z kolumnami zawierającymi wszystkie znaki „0”, takie jak „00000000”, ponieważ nigdy nie znaleziono znaku innego niż „0” do dopasowania.

Alternatywną techniką, którą widziałem, jest użycie TRIM:

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

Występuje to problem, jeśli istnieją osadzone spacje, ponieważ zostaną zamienione na „0”, gdy spacje zostaną zamienione z powrotem na „0”.

Staram się uniknąć skalarnego UDF. Znalazłem wiele problemów z wydajnością związanych z funkcjami UDF w SQL Server 2005.

Cade Roux
źródło
Czy reszta ciągu zawsze będzie zawierała tylko znaki „numeryczne”, czy też możesz mieć alfy? Jeśli to tylko dane liczbowe, to sugestia Quassnoi dotycząca rzutowania na liczbę całkowitą iz powrotem wydaje się trafna.
robsoft
To ogólna technika. Są to zazwyczaj numery kont, które pojawiają się w niezgodnym polu i muszę upewnić się, że są zgodne z regułami konformacji używanymi przez hurtownię danych w ich ETL (co oczywiście jest w znacznie bardziej w pełni funkcjonalnym środowisku SSIS, zakładam, że używają. TrimStart).
Cade Roux,

Odpowiedzi:

283
SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col))
Arvo
źródło
2
Sprytne, szkoda, że ​​nie pomyślałem o tym.
Cade Roux,
4
Nieważne, zdałem sobie sprawę, że „.” nie ma go w podciągu, ponieważ służy tylko do znalezienia wzoru - jest jeszcze sprytniejszy niż myślałem.
Cade Roux,
2
Zamknięcie tego w funkcji spowodowało spowolnienie moich zapytań. Nie jestem do końca pewien, dlaczego, ale myślę, że ma to związek z konwersją typów. Używanie wbudowanego SUBSTRING było znacznie szybsze.
Ronnie Overby,
1
Pytanie stwierdza, że ​​problem polega na tym, że kiedy analizujesz zero („0”), otrzymujesz pustą wartość. Musisz umieć odróżnić wartość „0” od wartości pustej. Zobacz mój post z pełnym rozwiązaniem: stackoverflow.com/a/21805081/555798
MikeTeeVee
1
@Arvo Wow ... Przez chwilę byłem zdezorientowany i myślałem, że odpowiedziałem na to pytanie, które miało mi pomóc. Pierwszy raz widziałem inny Arvona SO!
Arvo Bowen
41

Dlaczego po prostu nie rzucisz wartości na, INTEGERa potem z powrotem do VARCHAR?

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

--------
       0
Quassnoi
źródło
11
Jest to kolumna typu string, więc domyślam się, że od czasu do czasu oczekują danych nieliczbowych. Coś w rodzaju numeru MRN, w którym dane są głównie liczbowe.
Joel Coehoorn
1
Niestety, działa tylko dla danych liczbowych, a czasami łańcuchy również przekraczają zakres dla liczb całkowitych, więc musiałbyś użyć biginta.
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
Nawet z BIGINTniektórymi typami łańcuchów nadal nie uda się tej konwersji. Rozważmy 0001E123na przykład.
roaima
1
Z moich testów (i doświadczenia) wynika, że ​​jest to stosunkowo kosztowna operacja w porównaniu z przyjętą odpowiedzią. Ze względu na wydajność najlepiej jest unikać zmiany typów danych lub porównywania danych różnych typów, jeśli jest to w Twoich możliwościach.
reedstonefood
14

Inne odpowiedzi tutaj, aby nie brać pod uwagę, jeśli masz wszystkie zero (lub nawet jedno zero).
Niektórzy zawsze domyślnie ustawiają pusty ciąg na zero, co jest błędne, gdy ma pozostać puste.
Przeczytaj ponownie oryginalne pytanie. To odpowiada na to, czego chce Pytający.

Rozwiązanie nr 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"

Rozwiązanie nr 2 (z przykładowymi danymi):

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

Wyniki:

MikeTeeVee_SQL_Server_Remove_Leading_Zeros

Podsumowanie:

Możesz użyć tego, co mam powyżej, do jednorazowego usunięcia wiodących zer.
Jeśli planujesz często go używać, umieść go w funkcji Inline-Table-Valued (ITVF).
Twoje obawy dotyczące problemów z wydajnością UDF są zrozumiałe.
Jednak ten problem dotyczy tylko funkcji All-Scalar i Multi-Statement-Table Functions.
Używanie ITVF jest w porządku.

Mam ten sam problem z naszą bazą danych innej firmy.
W przypadku pól alfanumerycznych wielu jest wprowadzanych bez wiodących spacji, do cholery!
To sprawia, że ​​łączenie jest niemożliwe bez usunięcia brakujących zer wiodących.

Wniosek:

Zamiast usuwać zera wiodące, możesz rozważyć po prostu uzupełnienie przyciętych wartości zerami wiodącymi podczas łączenia.
Jeszcze lepiej, wyczyść dane w tabeli, dodając wiodące zera, a następnie odbuduj indeksy.
Myślę, że byłoby to DUŻO szybsze i mniej złożone.

SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF(' 0A10  ', ''))), 10)--0000000A10
SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF('', ''))), 10)--NULL --When Blank.
MikeTeeVee
źródło
4
@DiegoQueiroz Jeśli odpowiedź jest nieprawidłowa, obniż ocenę i wyjaśnij, dlaczego to nie działa. Jeśli odpowiedź działa, ale jest dla Ciebie zbyt wyczerpująca, nie obniżaj oceny mnie ani innych członków tej witryny. Dziękuje za komentarz. Dobrze jest to usłyszeć - mówię to szczerze.
MikeTeeVee
5

Zamiast spacji zamień 0 na „rzadki” biały znak, który normalnie nie powinien znajdować się w tekście kolumny. Podawanie wiersza jest prawdopodobnie wystarczające dla takiej kolumny. Następnie możesz normalnie LTrim i ponownie zamienić znak specjalny na 0.

Joel Coehoorn
źródło
3

Poniższe polecenie zwróci „0”, jeśli ciąg składa się wyłącznie z zer:

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
źródło
Zwróci to również zero, jeśli wartość nie zawiera zer (jest pusta).
MikeTeeVee
dlaczego jest str_col + '.' i nie tylko str_col? Co robi kropka?
Muflix,
2

To jest fajna funkcja ....

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
źródło
Zwróci to również zero, jeśli wartość nie zawiera zer (jest pusta). Ta odpowiedź używa również funkcji skalarnej z wieloma instrukcjami, gdy powyższe pytanie wyraźnie stwierdza, że ​​należy unikać używania funkcji UDF.
MikeTeeVee,
2

cast (wartość jak int) będzie zawsze działać, jeśli łańcuch jest liczbą

tichra
źródło
To nie daje odpowiedzi na pytanie. Aby skrytykować lub poprosić autora o wyjaśnienie, zostaw komentarz pod jego postem. - Z recenzji
Josip Ivic
1
w rzeczywistości jest to odpowiedź, ponieważ działa? odpowiedzi nie muszą być długie
tichra
Masz rację, że odpowiedzi nie muszą być długie, jednak powinny być kompletne, jeśli to możliwe, a Twoja odpowiedź nie jest; zmienia typ danych wyniku. Uważam, że byłaby to lepsza odpowiedź: SELECT CAST (CAST (value AS Int) AS VARCHAR). Powinieneś także wspomnieć, że otrzymasz błąd z Int, jeśli obliczona wartość przekroczy 2,1x10 ^ 9 (ośmiocyfrowe ograniczenie). Używając BigInt, otrzymasz błąd, jeśli wartość przekracza około 19 cyfr (9,2x10 ^ 18).
J. Chris Compton,
2

Moja wersja jest adaptacją pracy Arvo, z dodatkiem trochę więcej, aby zapewnić dwa inne przypadki.

1) Jeśli mamy wszystkie zera, powinniśmy zwrócić cyfrę 0.

2) Jeśli mamy spację, powinniśmy nadal zwracać pusty znak.

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
źródło
1
replace(ltrim(replace(Fieldname.TableName, '0', '')), '', '0')

Sugestia Thomasa G. spełniła nasze potrzeby.

Pole w naszym przypadku było już ciągiem i trzeba było przyciąć tylko zera wiodące. Przeważnie wszystko jest numeryczne, ale czasami są litery, więc poprzednia konwersja INT się zawiesiła.

Niespokojny
źródło
Nie, to
odcina
1
SELECT CAST(CAST('000000000' AS INTEGER) AS VARCHAR)

Ma to ograniczenie długości łańcucha, który można przekonwertować na INT

Curt Ehrhart
źródło
Czy możesz wyjaśnić nieco więcej w swojej odpowiedzi, dlaczego uważasz, że to zadziała? Co by się stało, gdyby była to liczba niezerowa i kilka wiodących zer?
Taegost
Jeśli Twoje liczby mają 18 cyfr lub mniej (a większość liczb 19-cyfrowych działa, ponieważ limit wynosi w rzeczywistości 9,2x10 ^ 18), możesz użyć SELECT CAST (CAST (@Field_Name AS BigInt) AS VARCHAR), aby pozbyć się zer wiodących. UWAGA: to się nie powiedzie, jeśli masz znaki nienumeryczne (myślnik, litera, kropka itp.) Z komunikatem o błędzie 8114 „Błąd podczas konwersji typu danych varchar na bigint”.
J. Chris Compton
1

Jeśli używasz Snowflake SQL, możesz użyć tego:

ltrim(str_col,'0')

Funkcja ltrim usuwa wszystkie instancje wyznaczonego zestawu znaków z lewej strony.

Więc ltrim (str_col, '0') on '00000008A' zwróci '8A'

A rtrim (str_col, „0.”) Na „$ 125.00” zwróci „$ 125”

JJFord3
źródło
1
  SUBSTRING(str_col, IIF(LEN(str_col) > 0, PATINDEX('%[^0]%', LEFT(str_col, LEN(str_col) - 1) + '.'), 0), LEN(str_col))

Działa dobrze nawet z „0”, „00” i tak dalej.

Lisandro
źródło
0

Spróbuj tego:

replace(ltrim(replace(@str, '0', ' ')), ' ', '0')
Shetty
źródło
0

Jeśli nie chcesz konwertować na int, wolę tę logikę poniżej, ponieważ może obsługiwać wartości null IFNULL (pole, LTRIM (pole, '0'))

fala uderzeniowa
źródło
0

W MySQL możesz to zrobić ...

Trim(Leading '0' from your_column)
joe_evans
źródło