SQL Server'da önde gelen sıfırları düzeltmek için daha iyi teknikler?


161

Ben kullanıyorum bu süre:

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

Ancak son zamanlarda, '00000000' gibi tüm "0" karakterleri olan sütunlarda bir sorun buldum çünkü hiçbir zaman "0" olmayan bir karakter bulamayacak.

Gördüğüm alternatif bir teknik TRIM:

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

Katıştırılmış boşluklar varsa bu bir sorun oluşturur, çünkü boşluklar tekrar "0" a çevrildiğinde bunlar "0" a dönüştürülür.

Skaler UDF'den kaçınmaya çalışıyorum. SQL Server 2005'te UDF'lerle ilgili birçok performans sorunu buldum.


Dizenin geri kalanı her zaman sadece 'sayısal' karakterler içerecek mi yoksa alfalarınız da olabilir mi? Eğer sadece sayısal verilerse, Quassnoi'nin bir tamsayıya ve arkasına döküm önerisi iyi bir veri gibi görünüyor.
robsoft

Genel bir teknik. Bunlar genellikle uyumsuz bir alanda gelen hesap numaralarıdır ve veri ambarının ETL'de kullandığı konfor kurallarına uymalarını sağlamalıyım (tabii ki çok daha tam özellikli SSIS ortamında, kullandıklarını varsayıyorum. TrimStart).
Cade Roux

Yanıtlar:


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

2
Zeki, keşke bunu düşünseydim.
Cade Roux

4
Boş ver, fark ettim ki '.' alt dizede değil çünkü sadece deseni bulmak için kullanılıyor - düşündüğümden daha akıllı.
Cade Roux

2
Bunu bir fonksiyonda kapsüllemek sorgularımı yavaşlatmaya neden oldu. Neden olduğundan tam olarak emin değilim ama bunun tür dönüşümüyle ilgili olduğunu düşünüyorum. SUBSTRING satır içi kullanımı çok daha hızlıydı.
Ronnie Overby

1
Soru, bununla ilgili sorunu, sıfır ('0') ayrıştırdığınızda bir boşluk elde ettiğinizdir. '0' değeri ile boş değer arasındaki farkı söyleyebilmeniz gerekir. Tam bir çözüm için lütfen yazıma bakın: stackoverflow.com/a/21805081/555798
MikeTeeVee

1
@Arvo Vay be ... Bir dakika kafam karıştı ve bana yardım etmek üzere olan bu soruyu cevapladığımı düşündüm. İlk kez ben ArvoSO başka gördüm !
Arvo Bowen

41

Neden sadece değeri INTEGERve sonra geri vermiyorsun VARCHAR?

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

--------
       0

11
Bu bir dize sütunu, bu yüzden zaman zaman sayısal olmayan veriler beklediklerini tahmin ediyorum. Verilerin yalnızca çoğunlukla sayısal olduğu MRN numarası gibi bir şey .
Joel Coehoorn

1
Ne yazık ki, yalnızca sayısal veriler için çalışır ve bazen dizeler tamsayı aralığını da aşar, bu nedenle bigint kullanmanız gerekir.
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

Bununla birlikte BIGINT, bazı dize türleri bu dönüştürme işleminde başarısız olur. 0001E123Örneğin düşünün .
roaima

1
Testlerimden (ve deneyimlerimden) bu, kabul edilen cevaba kıyasla nispeten maliyetli bir işlemdir. Performans nedenleriyle, veri türlerini değiştirmekten veya bunu yapmak için gücünüz dahilindeyse, farklı türlerdeki verileri karşılaştırmaktan kaçınmak en iyisidir.
reedstonefood

14

Burada sıfırlar (hatta tek bir sıfır) varsa diğer cevaplar dikkate alınmaz.
Bazıları her zaman boş bir dizeyi sıfıra ayarlar, bu da boş kalması gerektiğinde yanlıştır.
Orijinal soruyu tekrar okuyun. Bu, Soru Sahibinin istediklerini cevaplar.

Çözüm # 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"

Çözüm # 2 (örnek verilerle):

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

Sonuçlar:

MikeTeeVee_SQL_Server_Remove_Leading_Zeros

Özet:

Baştaki sıfırları kaldırmak için yukarıda sahip olduğum şeyleri kullanabilirsiniz.
Yeniden kullanmayı planlıyorsanız, bir Satır İçi-Tablo-Değerli İşlev (ITVF) içine yerleştirin.
UDF'lerle ilgili performans sorunlarına ilişkin endişeleriniz anlaşılabilir.
Ancak, bu sorun yalnızca All-Scalar-İşlevleri ve Çoklu Tablo-Tablo-İşlevleri için geçerlidir.
ITVF'leri kullanmak gayet iyi.

Üçüncü taraf veritabanımızda da aynı sorun var.
Alfa-Sayısal alanları ile birçok kişi önde gelen boşluklar olmadan girilir, insanlar!
Bu, eksik baştaki sıfırları temizlemeden birleştirmeleri imkansız hale getirir.

Sonuç:

Baştaki sıfırları kaldırmak yerine, birleşimlerinizi yaparken yalnızca kesilmiş değerlerinizi baştaki sıfırlarla doldurmayı düşünebilirsiniz.
Daha da iyisi, baştaki sıfırları ekleyerek ve ardından dizinlerinizi yeniden oluşturarak tablodaki verilerinizi temizleyin.
Bunun daha hızlı ve daha az karmaşık olacağını düşünüyorum.

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

4
@DiegoQueiroz Cevap yanlışsa, lütfen aşağı indirin ve neden işe yaramadığını açıklayın. Yanıt işe yarıyorsa, ancak sizin için çok kapsamlıysa, lütfen beni veya bu sitedeki diğer üyeleri indirmeyin. Yorum için teşekkürler. Bunu duymak iyi bir geri bildirim - bunu içtenlikle söylüyorum.
MikeTeeVee

5

Boşluk yerine, 0'ları normalde sütun metninde olmaması gereken 'nadir' boşluk karakteriyle değiştirin. Bir satır besleme muhtemelen böyle bir sütun için yeterince iyidir. Daha sonra normal olarak LTrim yapabilir ve özel karakteri tekrar 0'larla değiştirebilirsiniz.


3

Dize tamamen sıfırlardan oluşuyorsa aşağıdakiler '0' döndürür:

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

Değer ayrıca sıfır olmadığında da bu sıfır döndürür (boştur).
MikeTeeVee

neden str_col + 'var.' ve sadece str_col değil? Nokta ne yapar?
Muflix

2

Bu güzel bir Fonksiyon yapar ....

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

Değer ayrıca sıfır olmadığında da bu sıfır döndürür (boştur). Bu Soru, yukarıdaki Soru özellikle UDF'leri kullanmaktan kaçınacağını belirttiğinde, çoklu ifade-skaler-işlev kullanır.
MikeTeeVee

2

string bir sayı ise cast (int olarak değer) her zaman çalışır


Bu soruya bir cevap vermez. Bir yazardan eleştiri veya açıklama istemek için gönderilerinin altına bir yorum bırakın. - Şu kaynaktan
Josip Ivic

1
infact bu bir cevap çünkü işe yarıyor mu? cevapların uzun olması gerekmez
tichra

Cevapların uzun olması gerekmediğinden eminsiniz, ancak mümkünse eksiksiz olmalı ve cevabınız değil; sonucun veri türünü değiştirir. Bunun daha iyi bir yanıt olacağını düşünüyorum: SELECT CAST (CAST (değer AS Int) AS VARCHAR). Hesaplanan değer 2.1x10 ^ 9'u (sekiz basamaklı sınır) aşarsa Int ile ilgili bir hata alacağınızı da belirtmelisiniz. BigInt kullanarak, değer yaklaşık 19 haneyi (9.2x10 ^ 18) aşarsa hatayı alırsınız.
J. Chris Compton

2

Bunun benim versiyonum, Arvo'nun çalışmalarının bir uyarlaması, diğer iki vakayı sağlamak için biraz daha eklendi.

1) 0'ların hepsine sahipsek, 0 rakamını döndürmeliyiz.

2) Boşluk varsa, yine de boş bir karakter döndürmeliyiz.

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

Thomas G'nin önerisi bizim ihtiyaçlarımız için çalıştı.

Bizim durumumuzdaki alan zaten yaylıydı ve sadece önde gelen sıfırların kırpılması gerekiyordu. Çoğunlukla sayısaldır, ancak bazen önceki INT dönüşümünün çökmesi için harfler vardır.


Hayır, bu sondaki sıfırları bile düzeltir
Adam Ostrožlík

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

Bu, bir INT'ye dönüştürülebilecek dizginin uzunluğu için bir sınıra sahiptir.


Bunun neden işe yaradığını düşündüğünüze dair cevabınızda biraz daha açıklayabilir misiniz? Bu, bir grup önde gelen sıfır içeren sıfırdan farklı bir sayı olsaydı ne olurdu?
Taegost

Sayılarınız 18 basamak veya daha azsa (ve sınır aslında 9.2x10 ^ 18 olduğu için en fazla 19 basamaklı sayı çalışıyorsa), önde gelen sıfırlardan kurtulmak için SELECT CAST'ı (CAST (@Field_Name AS BigAR) AS VARCHAR) kullanabilirsiniz. Not: msg 8114 "varchar veri türü bigint'e dönüştürülürken hata."
J. Chris Compton

1

Snowflake SQL kullanıyorsanız, bunu kullanabilirsiniz:

ltrim(str_col,'0')

Ltrim işlevi, belirtilen karakter kümesinin tüm örneklerini sol taraftan kaldırır.

Yani '00000008A' üzerindeki ltrim (str_col, '0') '8A' döndürür

Ve '125,00' üzerindeki rtrim (str_col, '0.') '125 $' döndürür


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

'0', '00' vb.İle bile iyi çalışır.


0

Bunu dene:

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

0

Eğer int'e dönüştürmek istemiyorsanız, bu mantığı tercih ederim, çünkü nulls IFNULL (field, LTRIM (field, '0'))


0

MySQL'de bunu yapabilirsiniz ...

Trim(Leading '0' from your_column)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.