Кращі методи обрізки провідних нулів у 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" s, коли пробіли повернуті назад у "0" s.

Я намагаюся уникати скалярної АДС. У SQL Server 2005 я виявив багато проблем з продуктивністю UDF.


Чи завжди в решті рядка будуть міститись лише "числові" символи, чи у вас також є альфа? Якщо це просто числові дані, то пропозиція Quassnoi передати ціле число і назад здається непоганим.
robsoft

Це загальна техніка. Зазвичай це номери рахунків, які надходять у невідповідне поле, і мені потрібно переконатися, що вони відповідають правилам конформації, які використовує сховище даних у своєму ETL (що, звичайно, є у набагато більш повнофункціональному середовищі SSIS, я вважаю, що вони використовують. TrimStart).
Кейд Ру

Відповіді:


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

2
Розумний, хотілося б, щоб я про це думав.
Кейд Ру

4
Неважливо, я зрозумів, що "." не в підрядку, тому що це тільки для пошуку шаблону - це ще розумніше, ніж я думав.
Кейд Ру

2
Інкапсулювання цього функції призвело до уповільнення моїх запитів. Я не зовсім впевнений, чому, але я думаю, що це стосується перетворення типів. Використання SUBSTRING inline було набагато швидше.
Ронні Овербі

1
У запитанні вказано, що проблема з цим полягає в тому, що коли ви розбираєте нуль ('0'), ви отримуєте пробіл. Вам потрібно вміти визначати різницю між значенням "0" і порожнім значенням. Будь ласка , дивіться мій пост для повного вирішення: stackoverflow.com/a/21805081/555798
MikeTeeVee

1
@Arvo Wow ... На хвилину я розгубився і подумав, що відповів на це запитання, яке збиралося допомогти мені. Перший раз я бачив іншого Arvoна SO!
Арво Боуен

41

Чому б вам просто не передати значення INTEGERта повернутись назад VARCHAR?

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

--------
       0

11
Це стовпчик рядків, тому я думаю, що час від часу вони очікують нечислових даних. Щось на зразок номера MRN, де дані лише здебільшого числові.
Joel Coehoorn

1
На жаль, працює лише для числових даних, а іноді рядки також перевищують діапазон для цілих чисел, тому вам доведеться використовувати bigint.
Кейд Ру

3
SELECT CASE ISNUMERIC(str_col) WHEN 1 THEN CAST(CAST(str_col AS BIGINT) AS VARCHAR(255)) ELSE str_col END
Юрій Рожовецький

Навіть при цьому 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

Підсумок:

Ви можете використати те, що я маю вище, для одноразового видалення "нульових".
Якщо ви плануєте багаторазово використовувати його, розмістіть його у функції вбудованої таблиці з оцінкою (ITVF).
Ваші занепокоєння щодо проблем з ефективністю UDF зрозумілі.
Однак ця проблема стосується лише всіх скалярних функцій та функцій багатосторонніх таблиць.
Використання ITVF - це цілком чудово.

У мене така ж проблема з нашою базою даних сторонніх організацій.
З Альфа-числовими полями багато хто вводиться без провідних просторів, бовтайте людей!
Це робить приєднання неможливим без очищення пропущених нульових нулів.

Висновок:

Замість того, щоб видаляти провідні нулі, можливо, ви захочете розглянути можливість заміщення обрізаних значень лідерами, коли ви приєднуєтесь.
Ще краще, очистіть свої дані в таблиці, додавши провідні нулі, а потім відновіть свої індекси.
Я думаю, це було б ШЛЯХО швидше і менш складним.

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

4
@DiegoQueiroz Якщо відповідь невірний, тоді будь ласка, поясніть, чому це не працює. Якщо відповідь спрацьовує, але занадто вичерпна для вас, то, будь ласка, не зважайте на мене та інших членів на цьому веб-сайті. Дякую за коментар Це добре відгуки, щоб почути - це я щиро кажу.
MikeTeeVee

5

Замість пробілу замініть 0 на "рідкісним" символом пробілу, який зазвичай не повинен бути у тексті стовпця. Лінійний канал, ймовірно, досить хороший для такого стовпця. Тоді ви можете LTrim звичайно та знову замінити спеціальний символ на 0.


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? Що робить точка?
Муфлікс

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 (значення як int) завжди працюватиме, якщо рядок є числом


Це не дає відповіді на запитання. Щоб критикувати або вимагати роз'яснення у автора, залиште коментар під їх публікацією. - З огляду
Йосип Івич

1
infact це відповідь, тому що це працює? відповіді не потрібно бути тривалими
tichra

Ви вірні, що відповіді не повинні бути тривалими, проте вони повинні бути повними, якщо це можливо, і ваша відповідь не є; це змінює тип даних результату. Я вважаю, що це було б кращою відповіддю: ВИБІРТИ КАСТ (CAST (значення AS Int) ЯК ВАРХАР). Також слід зазначити, що ви отримаєте помилку з Int, якщо обчислене значення перевищує 2,1x10 ^ 9 (восьмизначний ліміт). Використовуючи BigInt, ви отримуєте помилку, якщо значення перевищує приблизно 19 цифр (9,2x10 ^ 18).
Дж. Кріс Комптон

2

Моя версія цього варіанта - це адаптація роботи Арво, де ще трохи додано для забезпечення двох інших випадків.

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

Пропозиція від Томаса G працювала для наших потреб.

Поле в нашому випадку вже було рядковим і потрібно було обрізати лише провідні нулі. Здебільшого, це все числове значення, але іноді бувають букви, щоб попереднє перетворення INT було завершено.


Ні, це обробляє навіть
сліди

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, я віддаю перевагу цій нижче логіці, оскільки вона може обробляти нулі IFNULL (поле, LTRIM (поле, '0'))


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.