เทคนิคที่ดีกว่าสำหรับการตัดเลขศูนย์ชั้นนำใน SQL Server หรือไม่


161

ฉันใช้มันมาระยะหนึ่งแล้ว:

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

อย่างไรก็ตามเมื่อเร็ว ๆ นี้ฉันพบปัญหาเกี่ยวกับคอลัมน์ที่มีอักขระ "0" ทั้งหมดเช่น '00000000' เนื่องจากไม่พบอักขระที่ไม่ใช่ "0" ให้ตรงกัน

เทคนิคทางเลือกที่ฉันเคยเห็นคือการใช้TRIM:

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

ปัญหานี้มีปัญหาหากมีช่องว่างในตัวเพราะจะเปลี่ยนเป็น "0" เมื่อช่องว่างนั้นถูกเปลี่ยนกลับเป็น "0"

ฉันพยายามหลีกเลี่ยงสเกลาร์ UDF ฉันพบปัญหาเกี่ยวกับประสิทธิภาพการทำงานกับ UDF จำนวนมากใน SQL Server 2005


ส่วนที่เหลือของสตริงจะมีตัวอักษร 'ตัวเลข' เสมอหรือคุณอาจมีตัวอักษรด้วยหรือไม่ หากเป็นเพียงตัวเลขข้อมูล Quassnoi แนะนำให้ชี้ไปที่จำนวนเต็มและด้านหลังดูเหมือนว่าดี
robsoft

มันเป็นเทคนิคทั่วไป โดยทั่วไปแล้วเหล่านี้เป็นหมายเลขบัญชีที่มาในเขตข้อมูลที่ไม่เป็นไปตามข้อกำหนดและฉันต้องการตรวจสอบให้แน่ใจว่าพวกเขาตรงกับกฎโครงสร้างที่คลังข้อมูลใช้ใน ETL ของพวกเขา (ซึ่งแน่นอนว่าในสภาพแวดล้อม SSIS ที่มีคุณสมบัติครบถ้วนมากกว่านี้ TrimStart)
เคด Roux

คำตอบ:


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

2
ฉลาดหวังว่าฉันจะคิดอย่างนั้น
เคด Roux

4
ไม่เป็นไรฉันรู้ว่า '.' ไม่ได้อยู่ในสตริงย่อยเพราะใช้เพื่อค้นหารูปแบบเท่านั้น - มันฉลาดกว่าที่ฉันคิด
Cade Roux

2
การห่อหุ้มสิ่งนี้ในฟังก์ชั่นทำให้การค้นหาของฉันช้าลง ฉันไม่แน่ใจว่าทำไม แต่ฉันคิดว่ามันเกี่ยวกับการแปลงรูปแบบ การใช้ SUBSTRING อินไลน์นั้นเร็วขึ้นมาก
Ronnie Overby

1
คำถามระบุปัญหานี้คือเมื่อคุณแยกศูนย์ ('0') คุณจะได้รับว่างเปล่า คุณต้องสามารถบอกความแตกต่างระหว่างค่า '0' และค่าว่างได้ โปรดดูโพสต์ของฉันสำหรับการแก้ปัญหาอย่างเต็มรูปแบบ: stackoverflow.com/a/21805081/555798
MikeTeeVee

1
@Arvo Wow ... หนึ่งนาทีฉันสับสนและคิดว่าฉันตอบคำถามนี้ที่จะช่วยฉันออก ครั้งแรกที่ฉันได้เห็นอีกArvoใน SO!
Arvo Bowen

41

ทำไมคุณไม่เพียงแค่โยนค่าINTEGERและกลับไปที่VARCHAR?

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

--------
       0

11
มันเป็นคอลัมน์สตริงดังนั้นฉันเดาว่าพวกเขาคาดหวังว่าข้อมูลที่ไม่ใช่ตัวเลขเป็นครั้งคราว บางอย่างเช่นหมายเลข MRN ที่ข้อมูลเป็นตัวเลขส่วนใหญ่เท่านั้น
Joel Coehoorn

1
น่าเสียดายที่ใช้งานได้กับข้อมูลตัวเลขเท่านั้นและบางครั้งสตริงจะเกินช่วงของจำนวนเต็มเช่นกันดังนั้นคุณต้องใช้บิ๊กint
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

แม้จะมีBIGINTสตริงบางประเภทจะยังคงล้มเหลวในการแปลงนี้ ลองพิจารณา0001E123ตัวอย่าง
roaima

1
จากการทดสอบของฉัน (และประสบการณ์) การดำเนินการนี้ค่อนข้างแพงเมื่อเปรียบเทียบกับคำตอบที่ยอมรับได้ เพื่อเหตุผลด้านประสิทธิภาพคุณควรหลีกเลี่ยงการเปลี่ยนประเภทข้อมูลหรือเปรียบเทียบข้อมูลประเภทต่าง ๆ หากอยู่ในอำนาจของคุณ
reedstonefood

14

คำตอบอื่น ๆ ที่นี่เพื่อไม่พิจารณาหากคุณมีทั้งหมดเป็นศูนย์ (หรือแม้แต่ศูนย์เดียว)
บางคนมักจะเริ่มต้นสตริงที่ว่างเปล่าให้เป็นศูนย์ซึ่งผิดเมื่อมันควรจะยังคงว่างเปล่า
อ่านคำถามเดิมอีกครั้ง นี่เป็นคำตอบที่ผู้ถามต้องการ

โซลูชัน # 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"

โซลูชัน # 2 (พร้อมข้อมูลตัวอย่าง):

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

ผล:

MikeTeeVee_SQL_Server_Remove_Leading_Zeros

สรุป:

คุณสามารถใช้สิ่งที่ฉันมีด้านบนสำหรับการลบหนึ่งในศูนย์ชั้นนำ
หากคุณวางแผนที่จะนำมาใช้ซ้ำจำนวนมากให้วางไว้ใน Inline-Table-Valued-Function (ITVF)
ความกังวลของคุณเกี่ยวกับปัญหาประสิทธิภาพการทำงานกับ UDF นั้นเป็นที่เข้าใจได้
อย่างไรก็ตามปัญหานี้ใช้กับฟังก์ชั่นสเกลาร์ทั้งหมดและมัลติฟังก์ชั่นแบบตาราง
การใช้ ITVF นั้นดีมาก

ฉันมีปัญหาเดียวกันกับฐานข้อมูลบุคคลที่สามของเรา
ด้วยฟิลด์ที่เป็นตัวเลข - อัลฟ่าจำนวนมากถูกป้อนโดยไม่มีช่องว่างนำมนุษย์อันตราย!
สิ่งนี้ทำให้การเชื่อมต่อเป็นไปไม่ได้โดยไม่ต้องล้างเลขศูนย์นำหน้าที่ขาดหายไป

สรุป:

แทนการลบเลขนำหน้าคุณอาจต้องพิจารณาเพิ่มความสมบูรณ์ของค่าที่ตัดแต่งด้วยนำเลขศูนย์เมื่อคุณเข้าร่วม
ยังดีกว่าล้างข้อมูลของคุณในตารางโดยเพิ่มศูนย์นำหน้าแล้วสร้างดัชนีของคุณใหม่
ฉันคิดว่านี่จะเป็นวิธีที่เร็วและซับซ้อนน้อยกว่า

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

4
@DiegoQueiroz หากคำตอบนั้นไม่ถูกต้องกรุณา downrank และอธิบายว่าทำไมมันไม่ทำงาน หากคำตอบใช้ได้ผล แต่ครอบคลุมสำหรับคุณมากเกินไปโปรดอย่าลบล้างฉันหรือสมาชิกคนอื่น ๆ ในเว็บไซต์นี้ ขอบคุณสำหรับความคิดเห็น มันเป็นข้อเสนอแนะที่ดีที่จะได้ยิน - ฉันพูดแบบนี้อย่างจริงใจ
MikeTeeVee

5

แทนที่จะเว้นวรรคให้แทนที่ 0 ด้วยอักขระช่องว่าง 'หายาก' ซึ่งปกติไม่ควรอยู่ในข้อความของคอลัมน์ การป้อนบรรทัดอาจดีพอสำหรับคอลัมน์เช่นนี้ จากนั้นคุณสามารถ LTrim ได้ตามปกติและแทนที่อักขระพิเศษด้วย 0's อีกครั้ง


3

ต่อไปนี้จะส่งกลับ '0' หากสตริงประกอบด้วยศูนย์ทั้งหมด:

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

สิ่งนี้จะส่งกลับค่าศูนย์เมื่อค่าไม่มีศูนย์ (ว่าง)
MikeTeeVee

ทำไมมี str_col + '.' และไม่เพียง str_col? จุดนี้ทำอะไร?
Muflix

2

ทำให้ฟังก์ชั่นที่ดี ....

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

สิ่งนี้จะส่งกลับค่าศูนย์เมื่อค่าไม่มีศูนย์ (ว่าง) คำตอบนี้ยังใช้ฟังก์ชั่นหลายคำสั่งเมื่อคำถามข้างต้นระบุโดยเฉพาะเพื่อหลีกเลี่ยงการใช้ UDF
MikeTeeVee

2

cast (value as int) จะทำงานหากสตริงเป็นตัวเลข


สิ่งนี้ไม่ได้ให้คำตอบสำหรับคำถาม หากต้องการวิจารณ์หรือขอคำชี้แจงจากผู้แต่งโปรดแสดงความคิดเห็นใต้โพสต์ของพวกเขา - จากการรีวิว
Josip Ivic

1
infact เป็นคำตอบเพราะมันใช้งานได้? คำตอบไม่จำเป็นต้องมีความยาว
tichra

คุณถูกต้องว่าคำตอบไม่จำเป็นต้องมีความยาว แต่ควรจะสมบูรณ์ถ้าเป็นไปได้และคำตอบของคุณไม่ถูกต้อง มันเปลี่ยนชนิดข้อมูลของผลลัพธ์ ฉันเชื่อว่านี่น่าจะเป็นการตอบสนองที่ดีกว่า: SELECT CAST (CAST (ค่า AS Int) AS VARCHAR) คุณควรระบุว่าคุณจะได้รับข้อผิดพลาดกับ Int หากค่าที่คำนวณได้เกิน 2.1x10 ^ 9 (ขีด จำกัด แปดหลัก) ใช้ BigInt คุณจะได้รับข้อผิดพลาดหากค่าเกินกว่า 19 หลัก (9.2x10 ^ 18)
เจ. คริสคอมป์ตัน

2

เวอร์ชันของฉันนี้เป็นการดัดแปลงงานของ Arvo โดยเพิ่มเติมอีกเล็กน้อยเพื่อให้แน่ใจว่าอีกสองกรณี

1) ถ้าเรามี 0 ทั้งหมดเราควรคืนค่า 0 หลัก

2) ถ้าเรามีช่องว่างเราก็ควรคืนค่าตัวอักษรว่าง

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

คำแนะนำจากโทมัสจีทำงานตามความต้องการของเรา

เขตข้อมูลในกรณีของเราเป็นสตริงแล้วและจำเป็นต้องตัดค่าศูนย์นำหน้า ส่วนใหญ่เป็นตัวเลขทั้งหมด แต่บางครั้งก็มีตัวอักษรดังนั้นการแปลง INT ก่อนหน้าจะล้มเหลว


ไม่ภายนอกข้อมูลรถนี้ต่อท้ายศูนย์
Adam Ostrožlík

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

นี่เป็นข้อ จำกัด เกี่ยวกับความยาวของสตริงที่สามารถแปลงเป็น INT ได้


คุณช่วยอธิบายอีกเล็กน้อยในคำตอบของคุณว่าทำไมคุณคิดว่าสิ่งนี้จะได้ผล? จะเกิดอะไรขึ้นถ้านี่เป็นตัวเลขที่ไม่ใช่ศูนย์ที่มีศูนย์นำหน้า
Taegost

หากตัวเลขของคุณคือ 18 หลักหรือน้อยกว่า (และตัวเลขส่วนใหญ่ 19 หลักทำงานได้เนื่องจากขีด จำกัด คือ 9.2x10 ^ 18) คุณสามารถใช้ SELECT CAST (CAST (@Field_Name AS BigInt) AS VARCHAR) เพื่อกำจัดศูนย์นำ หมายเหตุ: สิ่งนี้จะล้มเหลวหากคุณมีอักขระที่ไม่ใช่ตัวเลข (เส้นประตัวอักษรระยะเวลา ฯลฯ ) ที่มีข้อผิดพลาด msg 8114 "ข้อผิดพลาดในการแปลงประเภทข้อมูล varchar เป็น bigint"
เจ. คริสคอมป์ตัน

1

หากคุณใช้ Snowflake SQL อาจใช้สิ่งนี้:

ltrim(str_col,'0')

ฟังก์ชัน ltrim จะลบอินสแตนซ์ทั้งหมดของชุดอักขระที่กำหนดจากด้านซ้าย

ดังนั้น ltrim (str_col, '0') ใน '00000008A' จะส่งคืน '8A'

และ rtrim (str_col, '0.') ที่ '$ 125.00' จะกลับมาที่ '$ 125'


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

ทำงานได้ดีแม้กับ '0', '00' และอื่น ๆ



0

หากคุณไม่ต้องการที่จะแปลงเป็น int ฉันชอบตรรกะด้านล่างนี้เพราะมันสามารถจัดการกับค่า Null IFNULL (field, LTRIM (field, '0'))


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.