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.


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))

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

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.

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.


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

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

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


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

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.


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

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


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'


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.


0

Tente o seguinte:

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

0

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


0

No MySQL você pode fazer isso ...

Trim(Leading '0' from your_column)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.