De meilleures techniques pour couper les zéros non significatifs dans SQL Server?

161

J'utilise ceci depuis un certain temps:

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

Cependant, récemment, j'ai trouvé un problème avec les colonnes avec tous les caractères "0" comme "00000000" car il ne trouve jamais un caractère non "0" correspondant.

Une technique alternative que j'ai vue est d'utiliser TRIM:

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

Cela pose un problème s'il y a des espaces incorporés, car ils seront transformés en "0" lorsque les espaces seront reconvertis en "0".

J'essaye d'éviter un UDF scalaire. J'ai trouvé de nombreux problèmes de performances avec les UDF dans SQL Server 2005.

Cade Roux
la source
Le reste de la chaîne ne contiendra-t-il toujours que des caractères «numériques», ou pourriez-vous également avoir des alphas? S'il ne s'agit que de données numériques, la suggestion de Quassnoi de transtyper en un entier et inversement semble être une bonne idée.
robsoft
C'est une technique générale. Ce sont généralement des numéros de compte qui arrivent dans un champ non conforme et je dois m'assurer qu'ils correspondent aux règles de conformation que l'entrepôt de données utilise dans leur ETL (qui est, bien sûr, dans l'environnement SSIS beaucoup plus complet, je suppose qu'ils utilisent. TrimStart).
Cade Roux

Réponses:

283
SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col))
Arvo
la source
2
Intelligent, j'aurais aimé y penser.
Cade Roux
4
Qu'à cela ne tienne, j'ai réalisé que le '.' n'est pas dans la sous-chaîne car il ne sert qu'à trouver le motif - c'est encore plus intelligent que je ne le pensais.
Cade Roux
2
L'encapsulation de cela dans une fonction a entraîné un ralentissement de mes requêtes. Je ne sais pas trop pourquoi mais je pense que cela a à voir avec la conversion de type. L'utilisation du SUBSTRING en ligne était beaucoup plus rapide.
Ronnie Over du
1
La question indique que le problème avec ceci est que lorsque vous analysez un zéro («0»), vous obtenez un blanc. Vous devez être en mesure de faire la différence entre une valeur «0» et une valeur vide. Veuillez consulter mon article pour une solution complète: stackoverflow.com/a/21805081/555798
MikeTeeVee
1
@Arvo Wow ... Pendant une minute, j'étais confus et j'ai pensé avoir répondu à cette question qui allait m'aider. La première fois que j'en ai vu un autre Arvosur SO!
Arvo Bowen
41

Pourquoi ne pas simplement attribuer la valeur à INTEGER, puis revenir à VARCHAR?

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

--------
       0
Quassnoi
la source
11
C'est une colonne de chaînes, donc je suppose qu'ils attendent des données non numériques de temps en temps. Quelque chose comme un numéro NRM où les données ne sont la plupart du temps numérique.
Joel Coehoorn
1
Malheureusement, ne fonctionne que pour les données numériques, et parfois les chaînes dépassent également la plage des entiers, vous devrez donc utiliser 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
Même avec BIGINT, certains types de chaîne échoueront toujours cette conversion. Considérez 0001E123par exemple.
roaima
1
D'après mes tests (et mon expérience), il s'agit d'une opération relativement coûteuse par rapport à la réponse acceptée. Pour des raisons de performances, il est préférable d'éviter de modifier les types de données ou de comparer des données de types différents, si cela est en votre pouvoir.
reedstonefood
14

D'autres réponses ici à ne pas prendre en compte si vous avez des zéros (ou même un seul zéro).
Certains mettent toujours par défaut une chaîne vide à zéro, ce qui est faux quand elle est censée rester vide.
Relisez la question initiale. Cela répond à ce que veut le questionneur.

Solution 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"

Solution n ° 2 (avec des exemples de données):

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

Résultats:

MikeTeeVee_SQL_Server_Remove_Leading_Zeros

Résumé:

Vous pouvez utiliser ce que j'ai ci-dessus pour une suppression ponctuelle des zéro non significatifs.
Si vous prévoyez de le réutiliser beaucoup, placez-le dans un Inline-Table-Valued-Function (ITVF).
Vos préoccupations concernant les problèmes de performance avec les UDF sont compréhensibles.
Toutefois, ce problème s'applique uniquement à All-Scalar-Functions et Multi-Statement-Table-Functions.
Utiliser ITVF est parfaitement bien.

J'ai le même problème avec notre base de données tierce.
Avec les champs alphanumériques, beaucoup sont entrés sans les espaces principaux, dang humains!
Cela rend les jointures impossibles sans nettoyer les zéros de tête manquants.

Conclusion:

Au lieu de supprimer les zéros non significatifs, vous pouvez envisager de simplement compléter vos valeurs découpées avec des zéros non significatifs lorsque vous effectuez vos jointures.
Mieux encore, nettoyez vos données dans le tableau en ajoutant des zéros non significatifs, puis en reconstruisant vos index.
Je pense que ce serait bien plus rapide et moins complexe.

SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF(' 0A10  ', ''))), 10)--0000000A10
SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF('', ''))), 10)--NULL --When Blank.
MikeTeeVee
la source
4
@DiegoQueiroz Si la réponse est erronée, veuillez décliner et expliquer pourquoi cela ne fonctionne pas. Si la réponse fonctionne, mais qu'elle est trop complète pour vous, veuillez ne pas me réduire ni moi ni les autres membres de ce site. Merci pour le commentaire. C'est un bon retour à entendre - je le dis sincèrement.
MikeTeeVee
5

Au lieu d'un espace, remplacez les 0 par un caractère d'espacement «rare» qui ne devrait normalement pas être dans le texte de la colonne. Un saut de ligne est probablement suffisant pour une colonne comme celle-ci. Ensuite, vous pouvez LTrim normalement et remplacer à nouveau le caractère spécial par des 0.

Joël Coehoorn
la source
3

Ce qui suit renverra '0' si la chaîne se compose entièrement de zéros:

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
la source
Cela renverra également zéro lorsque la valeur n'a pas de zéros (est vide).
MikeTeeVee
pourquoi il y a str_col + '.' et pas seulement str_col? Que fait le point?
Muflix
2

Cela fait une belle fonction ...

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
la source
Cela renverra également zéro lorsque la valeur n'a pas de zéros (est vide). Cette réponse utilise également une fonction scalaire multi-instruction, lorsque la question ci-dessus indique spécifiquement d'éviter d'utiliser les fonctions UDF.
MikeTeeVee
2

cast (valeur comme int) fonctionnera toujours si la chaîne est un nombre

tichra
la source
Cela ne répond pas à la question. Pour critiquer ou demander des éclaircissements à un auteur, laissez un commentaire sous sa publication. - De l'avis
Josip Ivic
1
en fait c'est une réponse parce que ça marche? les réponses n'ont pas besoin d'être longues
tichra
Vous avez raison de dire que les réponses n'ont pas besoin d'être longues, mais elles devraient être complètes si possible, et votre réponse ne l'est pas; il modifie le type de données du résultat. Je crois que cela aurait été une meilleure réponse: SELECT CAST (CAST (valeur AS Int) AS VARCHAR). Vous devez également mentionner que vous obtiendrez une erreur avec Int si la valeur calculée dépasse 2,1x10 ^ 9 (limite de huit chiffres). En utilisant BigInt, vous obtenez l'erreur si la valeur dépasse environ 19 chiffres (9,2x10 ^ 18).
J.Chris Compton
2

Ma version de ceci est une adaptation du travail d'Arvo, avec un peu plus ajouté pour assurer deux autres cas.

1) Si nous avons tous les 0, nous devons renvoyer le chiffre 0.

2) Si nous avons un blanc, nous devrions toujours renvoyer un caractère vide.

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

La suggestion de Thomas G a fonctionné pour nos besoins.

Le champ dans notre cas était déjà une chaîne et seuls les zéros de tête devaient être coupés. La plupart du temps, tout est numérique, mais parfois il y a des lettres, donc la conversion INT précédente plantait.

Excité
la source
Non, cela coupe même les zéros de fin
Adam Ostrožlík
1
SELECT CAST(CAST('000000000' AS INTEGER) AS VARCHAR)

Cela a une limite sur la longueur de la chaîne qui peut être convertie en INT

Curt Ehrhart
la source
Pouvez-vous expliquer un peu plus dans votre réponse pourquoi vous pensez que cela fonctionnera? Que se passerait-il s'il s'agissait d'un nombre différent de zéro avec un tas de zéros non significatifs?
Taegost le
Si vos nombres comportent 18 chiffres ou moins (et que la plupart des nombres à 19 chiffres fonctionnent parce que la limite est en fait de 9,2x10 ^ 18), vous pouvez utiliser SELECT CAST (CAST (@Field_Name AS BigInt) AS VARCHAR) pour supprimer les zéros non significatifs. REMARQUE: cela échouera si vous avez des caractères non numériques (tiret, lettre, point, etc.) avec l'erreur msg 8114 "Erreur lors de la conversion du type de données varchar en bigint."
J.Chris Compton
1

Si vous utilisez Snowflake SQL, vous pouvez utiliser ceci:

ltrim(str_col,'0')

La fonction ltrim supprime toutes les instances du jeu de caractères désigné du côté gauche.

Donc ltrim (str_col, '0') sur '00000008A' renverrait '8A'

Et rtrim (str_col, '0.') Sur '125,00 $' renverrait '125 $'

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

Fonctionne bien même avec «0», «00» et ainsi de suite.

Lisandro
la source
0

Essaye ça:

replace(ltrim(replace(@str, '0', ' ')), ' ', '0')
Shetty
la source
0

Si vous ne souhaitez pas convertir en int, je préfère cette logique ci-dessous car elle peut gérer les valeurs nulles IFNULL (field, LTRIM (field, '0'))

onde de choc
la source
0

Dans MySQL, vous pouvez le faire ...

Trim(Leading '0' from your_column)
joe_evans
la source