Melhores técnicas para aparar zeros à esquerda no SQL Server?

161

Estou usando isso há algum tempo:

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

No entanto, recentemente, encontrei um problema com colunas com todos os caracteres "0", como '00000000', porque ele nunca encontra um caractere que não seja "0" para corresponder.

Uma técnica alternativa que eu vi é usar TRIM:

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

Isso tem um problema se houver espaços incorporados, porque eles serão transformados em "0" s quando os espaços forem transformados em "0" s.

Estou tentando evitar uma UDF escalar. Encontrei muitos problemas de desempenho com UDFs no SQL Server 2005.

Cade Roux
fonte
O resto da string sempre conterá apenas caracteres 'numéricos' ou você também pode ter alfas? Se são apenas dados numéricos, a sugestão de Quassnoi de converter para um número inteiro e voltar parece uma boa.
robsoft
É uma técnica geral. Normalmente, esses são números de contas que vêm em um campo não-conforme e eu preciso garantir que eles correspondam às regras de conformação que o armazém de dados usa em seu ETL (que é, obviamente, no ambiente SSIS muito mais completo, presumo que eles usem. TrimStart).
Cade Roux

Respostas:

282
SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col))
Arvo
fonte
2
Inteligente, gostaria de ter pensado nisso.
Cade Roux
4
Não importa, eu percebi que o '.' não está na substring porque é usado apenas para encontrar o padrão - é ainda mais inteligente do que eu pensava.
Cade Roux
2
Encapsular isso em uma função resultou em lentidão nas minhas consultas. Não sei por que, mas acho que tem a ver com conversão de tipo. Usar o SUBSTRING inline foi muito mais rápido.
Ronnie Overby
1
A pergunta afirma que o problema é que, quando você analisa um zero ('0'), você fica em branco. Você precisa saber a diferença entre um valor '0' e um valor em branco. Por favor, consulte o meu post para uma solução completa: stackoverflow.com/a/21805081/555798
MikeTeeVee
1
@ Arvo Wow ... Por um minuto fiquei confuso e pensei em responder a essa pergunta que estava prestes a me ajudar. Primeira vez que vi outro Arvono SO!
Arvo Bowen
41

Por que você simplesmente não lança o valor INTEGERe depois volta para VARCHAR?

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

--------
       0
Quassnoi
fonte
11
É uma coluna de string, então acho que eles esperam dados não numéricos de tempos em tempos. Algo como um número MRN em que os dados são apenas principalmente numéricos.
Joel Coehoorn
1
Infelizmente, funciona apenas para dados numéricos e, às vezes, as cadeias também excedem o intervalo para números inteiros; portanto, você precisará usar bigint.
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
Mesmo com BIGINTalguns tipos de string ainda falharão nessa conversão. Considere, 0001E123por exemplo.
roaima 2/09/2015
1
Dos meus testes (e experiência), essa é uma operação relativamente cara em comparação com a resposta aceita. Por motivos de desempenho, é melhor evitar alterar tipos de dados ou comparar dados de tipos diferentes, se estiver ao seu alcance.
Reedstonefood #
14

Outras respostas aqui para não levar em consideração se você tiver todos os zero (ou mesmo um único zero).
Alguns sempre usam como padrão uma string vazia como zero, o que é errado quando deveria permanecer em branco.
Releia a pergunta original. Isso responde ao que o Questionador deseja.

Solução 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"

Solução # 2 (com dados de amostra):

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

Resultados:

MikeTeeVee_SQL_Server_Remove_Leading_Zeros

Resumo:

Você pode usar o que tenho acima para remover os zero à esquerda.
Se você planeja reutilizá-lo muito, coloque-o em uma função com valor de tabela em linha (ITVF).
Suas preocupações sobre problemas de desempenho com UDFs são compreensíveis.
No entanto, esse problema se aplica apenas às funções All-Scalar e Functions-Table-Multi-Statement-Table.
O uso de ITVFs é perfeitamente adequado.

Eu tenho o mesmo problema com nosso banco de dados de terceiros.
Com os campos alfanuméricos, muitos são inseridos sem os espaços principais, humanos!
Isso impossibilita as junções sem limpar os zeros à esquerda ausentes.

Conclusão:

Em vez de remover os zeros à esquerda, considere apenas preencher os valores aparados com zeros antes de fazer as junções.
Melhor ainda, limpe seus dados na tabela adicionando zeros à esquerda e reconstruindo seus índices.
Eu acho que isso seria MUITO mais rápido e menos complexo.

SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF(' 0A10  ', ''))), 10)--0000000A10
SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF('', ''))), 10)--NULL --When Blank.
MikeTeeVee
fonte
4
@DiegoQueiroz Se a resposta estiver errada, por favor, rebaixe e explique por que ela não funciona. Se a resposta funcionar, mas for muito abrangente para você, não me prejudique ou outros membros neste site. Obrigado pelo comentário. É um bom feedback ouvir - digo isso sinceramente.
precisa saber é o seguinte
5

Em vez de um espaço, substitua os 0 por um caractere de espaço em branco 'raro' que normalmente não deveria estar no texto da coluna. Um feed de linha provavelmente é bom o suficiente para uma coluna como esta. Então você pode LTrim normalmente e substituir o caractere especial por 0 novamente.

Joel Coehoorn
fonte
3

O seguinte retornará '0' se a sequência consistir inteiramente em zeros:

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
fonte
Isso também retornará zero quando o valor não tiver zeros (estiver em branco).
21814 MikeTeeVee
por que existe str_col + '.' e não apenas str_col? O que o ponto faz?
Muflix
2

Isso faz uma boa função ....

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
fonte
Isso também retornará zero quando o valor não tiver zeros (estiver em branco). Esta resposta também usa uma função escalar com várias instruções, quando a pergunta acima especifica especificamente para evitar o uso de UDFs.
21814 MikeTeeVee
2

cast (valor como int) sempre funcionará se string for um número

tichra
fonte
Isso não fornece uma resposta para a pergunta. Para criticar ou solicitar esclarecimentos a um autor, deixe um comentário abaixo da postagem. - Do comentário
Josip Ivic
1
De fato, é uma resposta porque funciona? respostas não precisa ser demorado
tichra
Você está certo de que as respostas não precisam ser longas, mas devem ser completas, se possível, e sua resposta não; isso altera o tipo de dados do resultado. Acredito que essa teria sido uma resposta melhor: SELECT CAST (CAST (valor AS Int) AS VARCHAR). Você também deve mencionar que receberá um erro com Int se o valor calculado exceder 2.1x10 ^ 9 (limite de oito dígitos). Usando BigInt, você obtém o erro se o valor exceder cerca de 19 dígitos (9,2x10 x 18).
1911 Chris J. Compton
2

Minha versão disso é uma adaptação do trabalho de Arvo, com um pouco mais de acréscimo para garantir outros dois casos.

1) Se tivermos todos os 0s, devemos retornar o dígito 0.

2) Se temos um espaço em branco, ainda devemos retornar um caractere em branco.

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
fonte
1
replace(ltrim(replace(Fieldname.TableName, '0', '')), '', '0')

A sugestão de Thomas G funcionou para as nossas necessidades.

O campo no nosso caso já era string e apenas os zeros à esquerda precisavam ser aparados. Principalmente, é tudo numérico, mas às vezes existem letras para que a conversão anterior do INT travasse.

Randy
fonte
1
SELECT CAST(CAST('000000000' AS INTEGER) AS VARCHAR)

Isso tem um limite no comprimento da string que pode ser convertida em um INT

Curt Ehrhart
fonte
Você pode explicar um pouco mais a sua resposta sobre por que você acha que isso funcionará? O que aconteceria se esse fosse um número diferente de zero com um monte de zeros à esquerda?
Taegost 29/01
Se seus números tiverem 18 dígitos ou menos (e a maioria dos números de 19 dígitos funcionarem porque o limite é realmente 9,2x10 x 18), você poderá usar SELECT CAST (CAST (@Field_Name AS BigInt) AS VARCHAR) para se livrar dos zeros à esquerda. NOTA: isso falhará se você tiver caracteres não numéricos (traço, letra, ponto final etc.) com o erro msg 8114 "Erro ao converter o tipo de dados varchar em bigint."
1911 Chris J. Compton
1

Se você estiver usando o Snowflake SQL, poderá usar o seguinte:

ltrim(str_col,'0')

A função ltrim remove todas as instâncias do conjunto designado de caracteres do lado esquerdo.

Portanto, ltrim (str_col, '0') em '00000008A' retornaria '8A'

E rtrim (str_col, '0.') Em '$ 125,00' retornaria '$ 125'

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

Funciona bem mesmo com '0', '00' e assim por diante.

Lisandro
fonte
0

Tente o seguinte:

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

Se você não deseja converter em int, prefiro a lógica abaixo, pois ela pode manipular nulos IFNULL (campo, LTRIM (campo, '0'))

onda de choque
fonte
0

No MySQL você pode fazer isso ...

Trim(Leading '0' from your_column)
joe_evans
fonte