Teknik yang lebih baik untuk memangkas nol terkemuka di SQL Server?

161

Saya telah menggunakan ini selama beberapa waktu:

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

Namun baru-baru ini, saya menemukan masalah dengan kolom dengan semua karakter "0" seperti '00000000' karena tidak pernah menemukan karakter non- "0" yang cocok.

Teknik alternatif yang pernah saya lihat adalah menggunakan TRIM:

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

Ini memiliki masalah jika ada spasi yang disematkan, karena mereka akan diubah menjadi "0" ketika spasi diubah kembali menjadi "0".

Saya mencoba menghindari skalar UDF. Saya telah menemukan banyak masalah kinerja dengan UDF di SQL Server 2005.

Cade Roux
sumber
Apakah sisa string akan selalu berisi karakter 'numerik' saja, atau mungkin Anda memiliki alfabet juga? Jika itu hanya data numerik, maka saran Quassnoi untuk casting ke integer dan kembali sepertinya bagus.
robsoft
Ini teknik umum. Ini biasanya nomor akun yang datang di bidang yang tidak sesuai dan saya perlu memastikan mereka cocok dengan aturan konformasi yang digunakan gudang data dalam ETL mereka (yang, tentu saja di lingkungan SSIS yang jauh lebih berfitur lengkap, saya berasumsi mereka menggunakannya. TrimStart).
Cade Roux

Jawaban:

282
SUBSTRING(str_col, PATINDEX('%[^0]%', str_col+'.'), LEN(str_col))
Arvo
sumber
2
Pintar, seandainya aku memikirkan itu.
Cade Roux
4
Sudahlah, saya menyadari bahwa '.' tidak ada di substring karena hanya digunakan untuk menemukan polanya - itu bahkan lebih pintar dari yang saya kira.
Cade Roux
2
Meringkas ini dalam suatu fungsi menghasilkan memperlambat pertanyaan saya. Saya tidak begitu yakin mengapa tetapi saya pikir itu ada hubungannya dengan konversi jenis. Menggunakan inline SUBSTRING jauh lebih cepat.
Ronnie Overby
1
Pertanyaan menyatakan masalah dengan ini adalah ketika Anda menguraikan nol ('0'), Anda mendapatkan kosong. Anda harus dapat membedakan antara nilai '0' dan nilai kosong. Silakan lihat posting saya untuk solusi lengkap: stackoverflow.com/a/21805081/555798
MikeTeeVee
1
@Arvo Wow ... Sejenak saya bingung dan berpikir saya menjawab pertanyaan ini yang akan membantu saya. Pertama kali saya melihat yang lain Arvodi SO!
Arvo Bowen
41

Mengapa Anda tidak memberikan nilai saja INTEGERlalu kembali ke VARCHAR?

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

--------
       0
Quassnoi
sumber
11
Ini adalah kolom string, jadi saya kira mereka mengharapkan data non-numerik dari waktu ke waktu. Sesuatu seperti nomor MRN di mana data sebagian besar hanya berupa angka.
Joel Coehoorn
1
Sayangnya, hanya berfungsi untuk data numerik, dan terkadang string melebihi kisaran untuk integer juga, jadi Anda harus menggunakan 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
Bahkan dengan BIGINT, beberapa jenis string masih akan gagal konversi ini. Pertimbangkan 0001E123misalnya.
roaima
1
Dari pengujian saya (dan pengalaman) ini adalah operasi yang relatif mahal dibandingkan dengan jawaban yang diterima. Untuk alasan kinerja, yang terbaik adalah menghindari mengubah tipe data, atau membandingkan data dari tipe yang berbeda, jika itu sesuai kemampuan Anda untuk melakukannya.
reedstonefood 3-15
14

Jawaban lain di sini untuk tidak mempertimbangkan jika Anda memiliki semua-nol (atau bahkan nol).
Beberapa selalu default string kosong ke nol, yang salah ketika seharusnya tetap kosong.
Baca kembali pertanyaan aslinya. Ini menjawab apa yang diinginkan Penanya.

Solusi # 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"

Solusi # 2 (dengan data sampel):

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

Hasil:

MikeTeeVee_SQL_Server_Remove_Leading_Zeros

Ringkasan:

Anda dapat menggunakan apa yang saya miliki di atas untuk menghapus satu-nol dari memimpin-nol.
Jika Anda berencana untuk sering menggunakannya kembali, maka letakkan di Inline-Table-Valued-Function (ITVF).
Kekhawatiran Anda tentang masalah kinerja dengan UDF dapat dimengerti.
Namun, masalah ini hanya berlaku untuk Semua-Skalar-Fungsi dan Multi-Statement-Table-Functions.
Menggunakan ITVF tidak apa-apa.

Saya memiliki masalah yang sama dengan database Pihak ke-3 kami.
Dengan bidang Alpha-Numerik banyak yang masuk tanpa ruang terkemuka, sial manusia!
Ini membuat sambungan tidak mungkin tanpa membersihkan nol-awal yang hilang.

Kesimpulan:

Alih-alih menghapus leading-nol, Anda mungkin ingin mempertimbangkan hanya melapisi nilai-nilai Anda yang dipangkas dengan leading-nol saat Anda bergabung.
Lebih baik lagi, bersihkan data Anda di tabel dengan menambahkan angka nol di depan, lalu bangun kembali indeks Anda.
Saya pikir ini akan menjadi WAY lebih cepat dan tidak terlalu rumit.

SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF(' 0A10  ', ''))), 10)--0000000A10
SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF('', ''))), 10)--NULL --When Blank.
MikeTeeVee
sumber
4
@DiegoQueiroz Jika jawabannya salah, silakan turunkan dan jelaskan mengapa itu tidak berhasil. Jika jawabannya berhasil, tetapi terlalu komprehensif untuk Anda, maka tolong jangan menurunkan saya atau anggota lain di situs ini. Terima kasih atas komentarnya. Ini adalah umpan balik yang bagus untuk didengar - saya mengucapkan ini dengan tulus.
MikeTeeVee
5

Alih-alih spasi ganti 0 dengan karakter spasi putih 'langka' yang biasanya tidak ada dalam teks kolom. Umpan baris mungkin cukup baik untuk kolom seperti ini. Maka Anda dapat LTrim secara normal dan mengganti karakter khusus dengan 0 lagi.

Joel Coehoorn
sumber
3

Berikut ini akan mengembalikan '0' jika string seluruhnya terdiri dari nol:

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
sumber
Ini juga akan mengembalikan nol ketika nilainya tidak nol (kosong).
MikeTeeVee
mengapa ada str_col + '.' dan bukan hanya str_col? Apa yang dilakukan titik?
Muflix
2

Ini membuat Fungsi yang bagus ....

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
pengguna2600313
sumber
Ini juga akan mengembalikan nol ketika nilainya tidak nol (kosong). Jawaban ini juga menggunakan fungsi multi-pernyataan-skalar, ketika Pertanyaan di atas secara khusus menyatakan untuk menghindari penggunaan UDF.
MikeTeeVee
2

cast (value as int) akan selalu berfungsi jika string adalah angka

tichra
sumber
Ini tidak memberikan jawaban untuk pertanyaan itu. Untuk mengkritik atau meminta klarifikasi dari penulis, tinggalkan komentar di bawah posting mereka. - Dari Ulasan
Josip Ivic
1
Infact itu adalah jawaban karena itu berfungsi? jawaban tidak perlu panjang
tichra
Anda benar bahwa jawaban tidak perlu panjang, namun harus lengkap jika memungkinkan, dan jawaban Anda tidak; itu mengubah tipe data hasilnya. Saya percaya ini akan menjadi respons yang lebih baik: SELECT CAST (CAST (value AS Int) AS VARCHAR). Anda juga harus menyebutkan bahwa Anda akan mendapatkan kesalahan dengan Int jika nilai yang dihitung melebihi 2,1x10 ^ 9 (batas delapan digit). Menggunakan BigInt Anda mendapatkan kesalahan jika nilainya melebihi sekitar 19 digit (9.2x10 ^ 18).
J. Chris Compton
2

Versi saya tentang ini adalah adaptasi dari karya Arvo, dengan sedikit lebih banyak ditambahkan untuk memastikan dua kasus lainnya.

1) Jika kita memiliki semua 0s, kita harus mengembalikan digit 0.

2) Jika kita memiliki karakter kosong, kita masih harus mengembalikan karakter kosong.

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

Saran dari Thomas G bekerja untuk kebutuhan kita.

Bidang dalam kasus kami sudah string dan hanya nol terkemuka yang perlu dipangkas. Sebagian besar semuanya numerik tetapi kadang-kadang ada huruf sehingga konversi INT sebelumnya akan macet.

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

Ini memiliki batas panjang string yang dapat dikonversi menjadi INT

Curt Ehrhart
sumber
Bisakah Anda menjelaskan sedikit lebih dalam jawaban Anda mengapa Anda pikir ini akan berhasil? Apa yang akan terjadi jika ini adalah angka bukan nol dengan sekelompok nol di depan?
Taegost
Jika angka Anda 18 digit atau kurang (dan sebagian besar angka 19 digit berfungsi karena batasnya sebenarnya 9,2x10 ^ 18), Anda dapat menggunakan SELECT CAST (CAST (@Field_Name AS BigInt) AS VARCHAR) untuk menyingkirkan angka nol di depan. CATATAN: ini akan gagal jika Anda memiliki karakter non-numerik (tanda hubung, huruf, titik, dll.) Dengan pesan kesalahan 8114 "Kesalahan mengubah data tipe varchar ke bigint."
J. Chris Compton
1

Jika Anda menggunakan Snowflake SQL, gunakan ini:

ltrim(str_col,'0')

Fungsi ltrim menghapus semua instance dari set karakter yang ditunjuk dari sisi kiri.

Jadi ltrim (str_col, '0') pada '00000008A' akan mengembalikan '8A'

Dan rtrim (str_col, '0.') Pada '$ 125.00' akan mengembalikan '$ 125'

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

Bekerja dengan baik bahkan dengan '0', '00' dan seterusnya.

Lisandro
sumber
0

Coba ini:

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

Jika Anda tidak ingin mengubahnya menjadi int, saya lebih suka logika di bawah ini karena dapat menangani nulls IFNULL (bidang, LTRIM (bidang, '0'))

gelombang kejut
sumber
0

Di MySQL Anda dapat melakukan ini ...

Trim(Leading '0' from your_column)
joe_evans
sumber