web-dev-qa-db-fra.com

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

J'utilise this depuis quelque temps:

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

Cependant, récemment, j'ai rencontré un problème avec les colonnes contenant tous les caractères "0", comme "00000000", car il ne trouve jamais de caractère autre que "0".

Une technique alternative que j'ai vue consiste à utiliser TRIM:

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

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

J'essaie d'éviter une FDU scalaire. J'ai constaté de nombreux problèmes de performances avec les fonctions définies par l'utilisateur dans SQL Server 2005.

125
Cade Roux
SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col))
221
Arvo

Pourquoi ne lancez-vous pas simplement la valeur sur INTEGER et ensuite sur VARCHAR?

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

--------
       0
26
Quassnoi

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

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 exemple 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 décrit ci-dessus pour éliminer de manière ponctuelle les zéro non significatifs.
Si vous envisagez de le réutiliser beaucoup, placez-le dans une fonction Inline-Table-Valued-Function (ITVF).
Vos préoccupations concernant les problèmes de performance liés aux fonctions définies par l'utilisateur sont compréhensibles.
Cependant, ce problème s’applique uniquement à toutes les fonctions scalaires et aux fonctions multi-instructions.
L’utilisation de ITVF convient parfaitement.

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, humains!
Cela rend les jointures impossibles sans nettoyer les zéros de tête manquants.

Conclusion:

Au lieu de supprimer les zéros de gauche, vous pouvez envisager de simplement remplir vos valeurs ajustées avec des zéros de tête lorsque vous effectuez vos jointures.
Mieux encore, nettoyez vos données dans la table en ajoutant des zéros non significatifs, puis en reconstruisant vos index.
Je pense que ce serait beaucoup plus rapide et moins complexe.

SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF(' 0A10  ', ''))), 10)--0000000A10
SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF('', ''))), 10)--NULL --When Blank.
12
MikeTeeVee

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

5
Joel Coehoorn

Les éléments suivants renvoient «0» si la chaîne est entièrement composée 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
3
Scott

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
2
user2600313

cast (value as int) fonctionnera toujours si string est un nombre

2
tichra
replace(ltrim(replace(Fieldname.TableName, '0', '')), '', '0')

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

Dans notre cas, le champ était déjà une chaîne et seuls les zéros à gauche devaient être ajustés. La plupart du temps, tout est numérique, mais il y a parfois des lettres pour empêcher la conversion INT précédente de planter. 

1
Randy

Si vous utilisez Snowflake SQL, utilisez peut-être ceci:

ltrim(str_col,'0')

La fonction ltrim supprime toutes les occurrences 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 $'

0
JJFord3

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

1) Si nous avons tous les 0, nous devrions retourner le chiffre 0.

2) Si nous avons un blanc, nous devrions quand même retourner un caractère blanc.

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
0
Brisbe
SELECT CAST(CAST('000000000' AS INTEGER) AS VARCHAR)

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

0
Curt Ehrhart

Si vous ne voulez pas convertir en int, je préfère cette logique ci-dessous car elle peut gérer des valeurs NULL IFNULL (champ, LTRIM (champ, '0')) 

0
shockwave