Các kỹ thuật tốt hơn để cắt các số không hàng đầu trong SQL Server?


161

Tôi đã sử dụng điều này một thời gian:

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

Tuy nhiên, gần đây, tôi đã tìm thấy sự cố với các cột có tất cả các ký tự "0" như '00000000' vì nó không bao giờ tìm thấy ký tự không "0" nào khớp.

Một kỹ thuật thay thế mà tôi đã thấy là sử dụng TRIM:

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

Điều này có vấn đề nếu có các khoảng trắng được nhúng, bởi vì chúng sẽ bị biến thành "0" khi các khoảng trắng được chuyển thành "0" s.

Tôi đang cố gắng tránh một UDF vô hướng. Tôi đã tìm thấy rất nhiều vấn đề về hiệu năng với UDF trong SQL Server 2005.


Là phần còn lại của chuỗi sẽ luôn chỉ chứa các ký tự 'số', hoặc bạn cũng có thể có bảng chữ cái? Nếu đó chỉ là dữ liệu số, thì đề xuất của Quassnoi về việc truyền tới một số nguyên và trở lại có vẻ như là một dữ liệu tốt.
robsoft

Đó là một kỹ thuật chung. Đây thường là những số tài khoản đang đến trong một trường không phù hợp và tôi cần đảm bảo chúng phù hợp với quy tắc định dạng mà kho dữ liệu sử dụng trong ETL của chúng (tất nhiên là trong môi trường SSIS đầy đủ tính năng hơn, tôi cho rằng chúng sử dụng. Cắt tỉa).
Cade Roux

Câu trả lời:


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

2
Thông minh, ước gì tôi nghĩ về điều đó.
Cade Roux

4
Không sao, tôi nhận ra rằng '.' không có trong chuỗi con bởi vì nó chỉ được sử dụng để tìm mẫu - nó thậm chí còn thông minh hơn tôi nghĩ.
Cade Roux

2
Đóng gói điều này trong một chức năng dẫn đến làm chậm các truy vấn của tôi. Tôi không chắc tại sao nhưng tôi nghĩ nó phải làm với chuyển đổi loại. Sử dụng nội tuyến SUBSTRING nhanh hơn nhiều.
Ronnie Overby 26/07/13

1
Câu hỏi nêu vấn đề với điều này là khi bạn phân tích số 0 ('0'), bạn sẽ có một khoảng trống. Bạn cần có thể cho biết sự khác biệt giữa giá trị '0' và giá trị trống. Xin xem bài của tôi cho một giải pháp đầy đủ: stackoverflow.com/a/21805081/555798
MikeTeeVee

1
@Arvo Wow ... Trong một phút, tôi đã bối rối và nghĩ rằng tôi đã trả lời câu hỏi này để giúp tôi giải quyết. Lần đầu tiên tôi đã thấy một cái khác Arvotrên SO!
Arvo Bowen

41

Tại sao bạn không bỏ giá trị INTEGERvà sau đó quay lại VARCHAR?

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

--------
       0

11
Đó là một cột chuỗi, vì vậy thỉnh thoảng tôi đoán rằng họ đang mong đợi dữ liệu không phải là số. Một cái gì đó giống như một số MRN trong đó dữ liệu chỉ chủ yếu là số.
Joel Coehoorn

1
Thật không may, chỉ hoạt động cho dữ liệu số và đôi khi các chuỗi cũng vượt quá phạm vi cho số nguyên, vì vậy bạn phải sử dụng 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

Ngay cả với BIGINT, một số loại chuỗi vẫn sẽ thất bại trong việc chuyển đổi này. Hãy xem xét 0001E123ví dụ.
roaima

1
Từ thử nghiệm của tôi (và kinh nghiệm), đây là một hoạt động tương đối tốn kém so với câu trả lời được chấp nhận. Vì lý do hiệu suất, tốt nhất là tránh thay đổi loại dữ liệu hoặc so sánh dữ liệu của các loại khác nhau, nếu nó nằm trong khả năng của bạn để làm điều đó.
reedstonefood

14

Các câu trả lời khác ở đây không được xem xét nếu bạn có tất cả số không (hoặc thậm chí là một số không).
Một số luôn mặc định một chuỗi rỗng thành 0, sai khi nó được để trống.
Đọc lại câu hỏi ban đầu. Điều này trả lời những gì Người hỏi muốn.

Giải pháp số 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"

Giải pháp số 2 (với dữ liệu mẫu):

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

Các kết quả:

MikeTeeVee_Query_Server_Remove_Lead_Zeros

Tóm lược:

Bạn có thể sử dụng những gì tôi có ở trên để loại bỏ một lần của zero-zero.
Nếu bạn có kế hoạch tái sử dụng nó rất nhiều, thì hãy đặt nó vào Hàm Inline-Table-Valued-Function (ITVF).
Bạn lo lắng về các vấn đề về hiệu suất với UDF là điều dễ hiểu.
Tuy nhiên, vấn đề này chỉ áp dụng cho Hàm đa hướng và Hàm đa bảng-Hàm.
Sử dụng ITVF là hoàn toàn tốt.

Tôi có cùng một vấn đề với cơ sở dữ liệu của bên thứ 3 của chúng tôi.
Với các trường Alpha-Numeric, nhiều trường được đưa vào mà không có không gian hàng đầu, dang con người!
Điều này làm cho việc tham gia không thể thực hiện được mà không làm sạch các số 0 bị mất.

Phần kết luận:

Thay vì loại bỏ các số 0 đứng đầu, bạn có thể muốn xem xét chỉ đệm các giá trị đã cắt của mình với các số 0 đứng đầu khi bạn tham gia.
Tốt hơn hết, hãy dọn sạch dữ liệu của bạn trong bảng bằng cách thêm các số 0 đứng đầu, sau đó xây dựng lại các chỉ mục của bạn.
Tôi nghĩ rằng đây sẽ là CÁCH nhanh hơn và ít phức tạp hơn.

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

4
@DiegoQueiroz Nếu câu trả lời sai, vui lòng downrank và giải thích lý do tại sao nó không hoạt động. Nếu câu trả lời có hiệu quả, nhưng quá toàn diện đối với bạn, thì vui lòng không đánh giá thấp tôi hoặc các thành viên khác trên trang web này. Cảm ơn bạn đã bình luận. Đó là phản hồi tốt để nghe - tôi nói điều này một cách chân thành.
MikeTeeVee

5

Thay vì một khoảng trắng thay thế 0 bằng ký tự khoảng trắng 'hiếm' thường không có trong văn bản của cột. Một nguồn cấp dữ liệu có lẽ là đủ tốt cho một cột như thế này. Sau đó, bạn có thể LTrim bình thường và thay thế ký tự đặc biệt bằng 0 một lần nữa.


3

Sau đây sẽ trả về '0' nếu chuỗi bao gồm các số không:

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

Điều này cũng sẽ trả về 0 khi giá trị không có số không (để trống).
MikeTeeVee

tại sao có str_col + '.' và không chỉ str_col? Dấu chấm làm gì?
Muflix

2

Điều này làm cho một chức năng tốt đẹp ....

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

Điều này cũng sẽ trả về 0 khi giá trị không có số không (để trống). Câu trả lời này cũng sử dụng hàm đa câu lệnh, khi Câu hỏi ở trên nêu cụ thể để tránh sử dụng UDF.
MikeTeeVee

2

cast (value as int) sẽ luôn hoạt động nếu chuỗi là một số


Điều này không cung cấp một câu trả lời cho câu hỏi. Để phê bình hoặc yêu cầu làm rõ từ một tác giả, hãy để lại nhận xét bên dưới bài đăng của họ. - Từ đánh giá
Josip Ivic

1
nguyên vẹn nó là một câu trả lời vì nó không hoạt động? câu trả lời không cần dài dòng
tichra

Bạn đúng rằng câu trả lời không cần dài, tuy nhiên chúng phải đầy đủ nếu có thể, và câu trả lời của bạn thì không; nó thay đổi kiểu dữ liệu của kết quả. Tôi tin rằng đây sẽ là một phản hồi tốt hơn: CHỌN CAST (CAST (value AS Int) AS VARCHAR). Bạn cũng nên đề cập rằng bạn sẽ gặp lỗi với Int nếu giá trị được tính vượt quá 2.1x10 ^ 9 (giới hạn tám chữ số). Sử dụng BigInt, bạn sẽ gặp lỗi nếu giá trị vượt quá khoảng 19 chữ số (9.2x10 ^ 18).
J. Chris Compton

2

Phiên bản này của tôi là bản chuyển thể từ tác phẩm của Arvo, được thêm vào một chút để đảm bảo hai trường hợp khác.

1) Nếu chúng ta có tất cả 0, chúng ta nên trả về chữ số 0.

2) Nếu chúng ta có một khoảng trống, chúng ta vẫn nên trả về một ký tự trống.

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

Đề nghị từ Thomas G đã làm việc cho nhu cầu của chúng tôi.

Trường trong trường hợp của chúng tôi đã là chuỗi và chỉ các số 0 đứng đầu cần được cắt bớt. Chủ yếu là tất cả số nhưng đôi khi có các chữ cái để chuyển đổi INT trước đó sẽ bị sập.


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

Điều này có giới hạn về độ dài của chuỗi có thể được chuyển đổi thành INT


Bạn có thể giải thích thêm một chút trong câu trả lời của bạn về lý do tại sao bạn nghĩ rằng điều này sẽ làm việc? Điều gì sẽ xảy ra nếu đây là một con số khác không với một loạt các số 0 đứng đầu?
Taegost

Nếu số của bạn có 18 chữ số trở xuống (và hầu hết các số 19 chữ số hoạt động vì giới hạn thực sự là 9,2x10 ^ 18), bạn có thể sử dụng CHỌN CAST (CAST (@Field_Name AS BigInt) NHƯ VARCHAR) để loại bỏ các số 0 đứng đầu. LƯU Ý: điều này sẽ thất bại nếu bạn có các ký tự không phải là số (dấu gạch ngang, chữ cái, dấu chấm, v.v.) với thông báo lỗi 8114 "Lỗi chuyển đổi kiểu dữ liệu varchar thành bigint."
J. Chris Compton

1

Nếu bạn đang sử dụng Snowdrops SQL, có thể sử dụng điều này:

ltrim(str_col,'0')

Hàm ltrim loại bỏ tất cả các phiên bản của bộ ký tự được chỉ định từ phía bên trái.

Vì vậy, ltrim (str_col, '0') trên '00000008A' sẽ trả về '8A'

Và rtrim (str_col, '0.') Trên '$ 125,00' sẽ trả về '$ 125'


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

Hoạt động tốt ngay cả với '0', '00', v.v.


0

Thử cái này:

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

0

Nếu bạn không muốn chuyển đổi thành int, tôi thích logic này bên dưới vì nó có thể xử lý null IFNULL (trường, LTRIM (trường, '0'))


0

Trong MySQL bạn có thể làm điều này ...

Trim(Leading '0' from your_column)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.