Migliori tecniche per tagliare gli zeri iniziali in SQL Server?


161

Sto usando questo per un po 'di tempo:

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

Tuttavia, recentemente, ho riscontrato un problema con le colonne con tutti i caratteri "0" come "00000000" perché non trova mai un carattere diverso da "0" da abbinare.

Una tecnica alternativa che ho visto è di usare TRIM:

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

Questo ha un problema se ci sono spazi incorporati, perché saranno trasformati in "0" s quando gli spazi vengono riportati in "0" s.

Sto cercando di evitare un UDF scalare. Ho riscontrato molti problemi di prestazioni con UDF in SQL Server 2005.


Il resto della stringa conterrà sempre solo caratteri "numerici" o potresti avere anche delle alfa? Se sono solo dati numerici, allora il suggerimento di Quassnoi di lanciare un numero intero e viceversa sembra buono.
robsoft,

È una tecnica generale. Si tratta in genere di numeri di conto che arrivano in un campo non conformato e devo assicurarmi che corrispondano alle regole di conformità utilizzate dal data warehouse nel loro ETL (che è, ovviamente, nell'ambiente SSIS molto più completo, suppongo che usino. TrimStart).
Cade Roux,

Risposte:


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

2
Intelligente, vorrei averci pensato.
Cade Roux,

4
Poco male, mi sono reso conto che il "." non è nella sottostringa perché serve solo a trovare il modello - è persino più intelligente di quanto pensassi.
Cade Roux,

2
Incapsulare questo in una funzione ha provocato il rallentamento delle mie domande. Non sono del tutto sicuro del perché, ma penso che abbia a che fare con la conversione dei tipi. L'uso di SUBSTRING inline è stato molto più veloce.
Ronnie Overby, il

1
La domanda afferma che il problema è che quando si analizza uno zero ('0'), si ottiene uno spazio. Devi essere in grado di dire la differenza tra un valore '0' e un valore vuoto. Si prega di consultare il mio post per una soluzione completa: stackoverflow.com/a/21805081/555798
MikeTeeVee

1
@Arvo Wow ... Per un minuto sono stato confuso e ho pensato di rispondere a questa domanda che stava per aiutarmi. La prima volta che ne ho visto un altro Arvosu SO!
Arvo Bowen,

41

Perché non lanci semplicemente il valore su INTEGERe poi su VARCHAR?

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

--------
       0

11
È una colonna di stringhe, quindi immagino che di tanto in tanto si aspettino dati non numerici. Qualcosa come un numero MRN in cui i dati sono solo per lo più numerici.
Joel Coehoorn,

1
Sfortunatamente, funziona solo per i dati numerici e talvolta le stringhe superano l'intervallo anche per i numeri interi, quindi dovresti usare 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,

Anche con BIGINT, alcuni tipi di stringa falliranno comunque questa conversione. Consideriamo 0001E123ad esempio.
roaima,

1
Dal mio test (ed esperienza) questa è un'operazione relativamente costosa rispetto alla risposta accettata. Per motivi di prestazioni, è meglio evitare di modificare i tipi di dati o di confrontare dati di tipi diversi, se è possibile farlo.
reedstonefood,

14

Altre risposte qui da non prendere in considerazione se si hanno tutti zero (o anche un singolo zero).
Alcuni impostano sempre una stringa vuota a zero, che è errata quando dovrebbe rimanere vuota.
Rileggi la domanda originale. Questo risponde a ciò che l'interrogante vuole.

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

Soluzione n. 2 (con dati di esempio):

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

risultati:

MikeTeeVee_SQL_Server_Remove_Leading_Zeros

Sommario:

Potresti usare quello che ho sopra per una rimozione una tantum di zero iniziale.
Se hai intenzione di riutilizzarlo molto, inseriscilo in una Inline-Table-Valued-Function (ITVF).
Le tue preoccupazioni sui problemi di prestazioni con UDF sono comprensibili.
Tuttavia, questo problema si applica solo alle funzioni all-scalar e alle funzioni multi-statement-table.
L'uso di ITVF va benissimo.

Ho lo stesso problema con il nostro database di terze parti.
Con i campi alfanumerici molti sono inseriti senza gli spazi iniziali, perché gli umani!
Ciò rende impossibili i join senza eliminare gli zeri iniziali mancanti.

Conclusione:

Invece di rimuovere gli zeri iniziali, potresti prendere in considerazione solo il riempimento dei valori tagliati con zeri iniziali quando esegui i join.
Meglio ancora, ripulisci i tuoi dati nella tabella aggiungendo zeri iniziali, quindi ricostruendo i tuoi indici.
Penso che questo sarebbe MODO più veloce e meno complesso.

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

4
@DiegoQueiroz Se la risposta è sbagliata, per favore esegui il downrank e spiega perché non funziona. Se la risposta funziona, ma è troppo esaustiva per te, ti preghiamo di non effettuare il downrank di me o di altri membri su questo sito. Grazie per il commento. È un buon feedback sentire - lo dico sinceramente.
MikeTeeVee,

5

Invece di uno spazio sostituisci gli 0 con un carattere di spazio bianco "raro" che normalmente non dovrebbe essere nel testo della colonna. Un feed di riga è probabilmente abbastanza buono per una colonna come questa. Quindi puoi LTrim normalmente e sostituire nuovamente il carattere speciale con 0.


3

Quanto segue restituirà '0' se la stringa è interamente composta da zeri:

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

Questo restituirà anche zero quando il valore non ha zeri (è vuoto).
MikeTeeVee,

perché c'è str_col + '.' e non solo str_col? Cosa fa il punto?
Muflix,

2

Questo rende una bella funzione ....

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

Questo restituirà anche zero quando il valore non ha zeri (è vuoto). Questa risposta utilizza anche una funzione scalare multi-istruzione, quando la domanda precedente afferma specificamente di evitare l'uso di UDF.
MikeTeeVee,

2

cast (valore come int) funzionerà sempre se stringa è un numero


Questo non fornisce una risposta alla domanda. Per criticare o richiedere chiarimenti a un autore, lascia un commento sotto il suo post. - Dalla recensione
Josip Ivic,

1
infatti è una risposta perché funziona? le risposte non devono essere lunghe
tichra

Hai ragione nel dire che le risposte non devono essere lunghe, tuttavia dovrebbero essere complete se possibile, e la tua risposta no; cambia il tipo di dati del risultato. Credo che questa sarebbe stata una risposta migliore: SELECT CAST (CAST (valore AS Int) AS VARCHAR). Dovresti anche menzionare che otterrai un errore con Int se il valore calcolato supera 2,1x10 ^ 9 (limite di otto cifre). Usando BigInt si ottiene l'errore se il valore supera circa 19 cifre (9.2x10 ^ 18).
J. Chris Compton,

2

La mia versione di questo è un adattamento del lavoro di Arvo, con un po 'di più aggiunto per garantire altri due casi.

1) Se abbiamo tutti gli 0, dovremmo restituire la cifra 0.

2) Se abbiamo uno spazio vuoto, dovremmo comunque restituire un carattere vuoto.

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

Il suggerimento di Thomas G ha funzionato per le nostre esigenze.

Il campo nel nostro caso era già stringa e solo gli zeri iniziali dovevano essere tagliati. Principalmente è tutto numerico ma a volte ci sono lettere, quindi la precedente conversione INT andrebbe in crash.


No, questo elimina persino gli zeri finali
Adam Ostrožlík il

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

Ciò ha un limite sulla lunghezza della stringa che può essere convertita in un INT


Puoi spiegarci un po 'di più nella tua risposta sul perché pensi che funzionerà? Cosa accadrebbe se questo fosse un numero diverso da zero con un gruppo di zeri iniziali?
Taegost,

Se i tuoi numeri sono di 18 cifre o meno (e la maggior parte dei numeri di 19 cifre funziona perché il limite è in realtà 9,2x10 ^ 18) puoi usare SELECT CAST (CAST (@Field_Name AS BigInt) AS VARCHAR) per sbarazzarti degli zeri iniziali. NOTA: ciò non funzionerà se si hanno caratteri non numerici (trattino, lettera, punto, ecc.) Con errore msg 8114 "Errore durante la conversione del tipo di dati varchar in bigint."
J. Chris Compton,

1

Se si utilizza Snowflake SQL, è possibile utilizzare questo:

ltrim(str_col,'0')

La funzione ltrim rimuove tutte le istanze dell'insieme di caratteri designato dal lato sinistro.

Quindi ltrim (str_col, '0') su '00000008A' restituirà '8A'

E rtrim (str_col, '0.') Su '$ 125,00' restituirebbe '$ 125'


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

Funziona bene anche con '0', '00' e così via.


0

Prova questo:

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

0

Se non si desidera convertire in int, preferisco questo sotto la logica perché può gestire i null IFNULL (campo, LTRIM (campo, '0'))


0

In MySQL puoi farlo ...

Trim(Leading '0' from your_column)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.