Лучшие методы для обрезки лидирующих нулей в 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).
Кейд Ру

Ответы:

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на ТАК!
Арво Боуэн
41

Почему бы вам просто не привести значение к, INTEGERа затем вернуться к VARCHAR?

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

--------
       0
Quassnoi
источник
11
Это строковый столбец, поэтому я предполагаю, что они время от времени ожидают нечисловые данные. Что-то вроде номера MRN, где данные в основном только числовые.
Джоэл Коухорн
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для примера.
Роайма
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 понятны.
Однако эта проблема относится только к функциям All-Scalar-Functions и Multi-Statement-Table-Functions.
Использование ITVF совершенно нормально.

У меня та же проблема с нашей сторонней базой данных.
С помощью буквенно-цифровых полей многие вводятся без пробелов, черт возьми!
Это делает невозможным объединение без очистки отсутствующих ведущих нулей.

Вывод:

Вместо удаления начальных нулей, вы можете захотеть просто дополнить свои обрезанные значения начальными нулями, когда вы делаете свои объединения.
Еще лучше очистить данные в таблице, добавив начальные нули, а затем перестроив индексы.
Я думаю, что это будет гораздо быстрее и менее сложным.

SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF(' 0A10  ', ''))), 10)--0000000A10
SELECT RIGHT('0000000000' + LTRIM(RTRIM(NULLIF('', ''))), 10)--NULL --When Blank.
MikeTeeVee
источник
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? Что делает точка?
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
user2600313
источник
Это также вернет ноль, когда значение не имеет нулей (пусто). В этом ответе также используется скалярная функция с несколькими утверждениями, когда в приведенном выше вопросе конкретно говорится, чтобы избежать использования UDF.
MikeTeeVee
2

приведение (значение как int) всегда будет работать, если строка является числом

tichra
источник
Это не дает ответа на вопрос. Чтобы критиковать или запросить разъяснения у автора, оставьте комментарий под своим постом. - Из обзора
Иосип Ивич
1
на самом деле это ответ, потому что это работает? ответы не должны быть длинными
tichra
Вы правы в том, что ответы не должны быть длинными, однако, если возможно, они должны быть полными, а ваш ответ - нет; это меняет тип данных результата. Я считаю, что это был бы лучший ответ: ВЫБЕРИТЕ CAST (CAST (значение AS Int) AS VARCHAR). Вы также должны упомянуть, что вы получите ошибку с 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
Brisbe
источник
1
replace(ltrim(replace(Fieldname.TableName, '0', '')), '', '0')

Предложение Томаса Г. сработало для наших нужд.

Поле в нашем случае уже было строковым, и нужно было обрезать только начальные нули. В основном это все цифры, но иногда встречаются буквы, поэтому предыдущее преобразование 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'

JJFord3
источник
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

Попробуй это:

replace(ltrim(replace(@str, '0', ' ')), ' ', '0')
Шетти
источник
0

Если вы не хотите преобразовывать в int, я предпочитаю эту логику ниже, поскольку она может обрабатывать пустые значения IFNULL (field, LTRIM (field, '0'))

ударноволновая
источник
0

В MySQL вы можете сделать это ...

Trim(Leading '0' from your_column)
joe_evans
источник