¿Mejores técnicas para recortar los ceros iniciales en SQL Server?

161

He estado usando esto por algún tiempo:

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

Sin embargo, recientemente encontré un problema con las columnas con todos los caracteres "0" como '00000000' porque nunca encuentra un carácter que no sea "0" para que coincida.

Una técnica alternativa que he visto es usar TRIM:

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

Esto tiene un problema si hay espacios incrustados, porque se convertirán en "0" cuando los espacios se vuelvan a convertir en "0".

Estoy tratando de evitar un UDF escalar. He encontrado muchos problemas de rendimiento con UDF en SQL Server 2005.

Cade Roux
fuente
¿El resto de la cadena siempre contendrá solo caracteres 'numéricos', o podría tener también alfa? Si solo se trata de datos numéricos, entonces la sugerencia de Quassnoi de convertir a un entero y viceversa parece ser buena.
robsoft
Es una técnica general. Estos son típicamente números de cuenta que vienen en un campo no conformado y necesito asegurarme de que coincidan con las reglas de conformación que el almacén de datos usa en su ETL (que, por supuesto, en el entorno SSIS mucho más completo, supongo que usan). TrimStart).
Cade Roux

Respuestas:

282
SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col))
Arvo
fuente
2
Inteligente, desearía haber pensado en eso.
Cade Roux
44
No importa, me di cuenta de que el '.' no está en la subcadena porque solo se usa para encontrar el patrón, es aún más inteligente de lo que pensaba.
Cade Roux
2
Encapsular esto en una función resultó en ralentizar mis consultas. No estoy muy seguro de por qué, pero creo que tiene que ver con la conversión de tipos. Usar el SUBSTRING en línea fue mucho más rápido.
Ronnie Overby
1
La pregunta indica que el problema con esto es que cuando analizas un cero ('0'), obtienes un espacio en blanco. Debe poder distinguir la diferencia entre un valor '0' y un valor en blanco. Consulte mi publicación para obtener una solución completa: stackoverflow.com/a/21805081/555798
MikeTeeVee
1
@Arvo Wow ... Por un momento estaba confundido y pensé que respondía a esta pregunta que estaba a punto de ayudarme. Primera vez que he visto otro Arvoen SO!
Arvo Bowen
41

¿Por qué no echas el valor INTEGERy luego vuelves a VARCHAR?

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

--------
       0
Quassnoi
fuente
11
Es una columna de cadena, por lo que supongo que esperan datos no numéricos de vez en cuando. Algo así como un número MRN donde los datos son principalmente numéricos.
Joel Coehoorn el
1
Desafortunadamente, solo funciona para datos numéricos y, a veces, las cadenas también exceden el rango de enteros, por lo que tendría que 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
Incluso con BIGINTalgunos tipos de cadena fallarán esta conversión. Considere 0001E123por ejemplo.
roaima
1
Según mis pruebas (y experiencia), esta es una operación relativamente costosa en comparación con la respuesta aceptada. Por razones de rendimiento, es mejor evitar cambiar los tipos de datos o comparar datos de diferentes tipos, si está dentro de su alcance hacerlo.
Reedstonefood 03 de
14

Otras respuestas aquí para no tener en cuenta si tiene todo cero (o incluso un solo cero).
Algunos siempre predeterminan una cadena vacía a cero, lo cual es incorrecto cuando se supone que debe permanecer en blanco.
Vuelva a leer la pregunta original. Esto responde a lo que quiere el interrogador.

Solución # 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"

Solución # 2 (con datos de muestra):

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

Resumen:

Podrías usar lo que tengo arriba para una eliminación única de los ceros a la izquierda.
Si planea reutilizarlo mucho, colóquelo en una función de valor de tabla en línea (ITVF).
Sus preocupaciones sobre problemas de rendimiento con UDF son comprensibles.
Sin embargo, este problema solo se aplica a todas las funciones escalares y a las funciones de tabla de varias instrucciones.
Usar ITVF está perfectamente bien.

Tengo el mismo problema con nuestra base de datos de terceros.
¡Con los campos alfanuméricos se ingresan muchos sin los espacios iniciales, malditos humanos!
Esto hace que las uniones sean imposibles sin limpiar los ceros iniciales que faltan.

Conclusión:

En lugar de eliminar los ceros a la izquierda, puede considerar simplemente rellenar sus valores recortados con ceros a la izquierda cuando haga sus uniones.
Mejor aún, limpie sus datos en la tabla agregando ceros a la izquierda y luego reconstruyendo sus índices.
Creo que esto sería MUCHO más rápido y menos complejo.

SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF(' 0A10  ', ''))), 10)--0000000A10
SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF('', ''))), 10)--NULL --When Blank.
MikeTeeVee
fuente
44
@DiegoQueiroz Si la respuesta es incorrecta, descárguela y explique por qué no funciona. Si la respuesta funciona, pero es demasiado completa para usted, no me desanime ni a otros miembros de este sitio. Gracias por el comentario. Es un buen comentario escuchar, lo digo sinceramente.
MikeTeeVee
5

En lugar de un espacio, reemplace los 0 con un carácter de espacio en blanco 'raro' que normalmente no debería estar en el texto de la columna. Un avance de línea es probablemente lo suficientemente bueno para una columna como esta. Entonces puede LTrim normalmente y reemplazar el carácter especial con 0 nuevamente.

Joel Coehoorn
fuente
3

Lo siguiente devolverá '0' si la cadena consiste completamente en ceros:

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
fuente
Esto también devolverá cero cuando el valor no tiene ceros (está en blanco).
MikeTeeVee
por qué hay str_col + '.' y no solo str_col? ¿Qué hace el punto?
Muflix
2

Esto hace una buena función ...

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
usuario2600313
fuente
Esto también devolverá cero cuando el valor no tiene ceros (está en blanco). Esta respuesta también utiliza una función escalar de múltiples enunciados, cuando la pregunta anterior establece específicamente que se debe evitar el uso de UDF.
MikeTeeVee
2

cast (value as int) siempre funcionará si string es un número

tichra
fuente
Esto no proporciona una respuesta a la pregunta. Para criticar o solicitar una aclaración de un autor, deje un comentario debajo de su publicación. - De la opinión
Josip Ivic
1
de hecho es una respuesta porque funciona? las respuestas no necesitan ser largas
tichra
Tiene razón en que las respuestas no necesitan ser largas, sin embargo, deben ser completas si es posible, y su respuesta no lo es; cambia el tipo de datos del resultado. Creo que esto habría sido una mejor respuesta: SELECT CAST (CAST (value AS Int) AS VARCHAR). También debe mencionar que obtendrá un error con Int si el valor calculado excede 2.1x10 ^ 9 (límite de ocho dígitos). Usando BigInt obtienes el error si el valor supera los 19 dígitos (9.2x10 ^ 18).
J. Chris Compton
2

Mi versión de esto es una adaptación del trabajo de Arvo, con un poco más para asegurar otros dos casos.

1) Si tenemos todos los 0, deberíamos devolver el dígito 0.

2) Si tenemos un espacio en blanco, aún deberíamos devolver un carácter en blanco.

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

La sugerencia de Thomas G funcionó para nuestras necesidades.

El campo en nuestro caso ya era una cadena y solo los ceros iniciales necesitaban ser recortados. Principalmente es todo numérico, pero a veces hay letras, por lo que la conversión INT anterior se bloqueará.

Cachondo
fuente
No, esto recorta incluso ceros al final
Adam Ostrožlík
1
SELECT CAST(CAST('000000000' AS INTEGER) AS VARCHAR)

Esto tiene un límite en la longitud de la cadena que se puede convertir a un INT

Curt Ehrhart
fuente
¿Puedes explicar un poco más en tu respuesta por qué crees que esto funcionará? ¿Qué pasaría si este fuera un número distinto de cero con un montón de ceros a la izquierda?
Taegost el
Si sus números tienen 18 dígitos o menos (y la mayoría de los números de 19 dígitos funcionan porque el límite es en realidad 9.2x10 ^ 18), puede usar SELECT CAST (CAST (@Field_Name AS BigInt) AS VARCHAR) para deshacerse de los ceros iniciales. NOTA: esto fallará si tiene caracteres no numéricos (guión, letra, punto, etc.) con el mensaje de error 8114 "Error al convertir el tipo de datos varchar a bigint".
J. Chris Compton
1

Si está utilizando Snowflake SQL, puede usar esto:

ltrim(str_col,'0')

La función ltrim elimina todas las instancias del conjunto designado de caracteres del lado izquierdo.

Entonces ltrim (str_col, '0') en '00000008A' devolvería '8A'

Y rtrim (str_col, '0.') En '$ 125.00' devolvería '$ 125'

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

Funciona bien incluso con '0', '00' y así sucesivamente.

Lisandro
fuente
0

Prueba esto:

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

Si no desea convertir a int, prefiero esta lógica a continuación porque puede manejar nulos IFNULL (campo, LTRIM (campo, '0'))

onda de choque
fuente
0

En MySQL puedes hacer esto ...

Trim(Leading '0' from your_column)
joe_evans
fuente