在SQL Server中修剪前导零的更好技术?


161

我一直在使用一段时间:

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

但是最近,我发现包含“ 00000000”之类的所有“ 0”字符的列存在问题,因为它从未找到匹配的非“ 0”字符。

我见过的另一种技术是使用TRIM

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

如果存在嵌入的空格,则会出现问题,因为当空格变成“ 0”时,它们将变成“ 0”。

我试图避免标量UDF。我发现SQL Server 2005中的UDF有很多性能问题。


字符串的其余部分是否总是仅包含“数字”字符,或者您也可能具有字母?如果只是数字数据,那么Quassnoi的建议是将其转换为整数并返回。
robsoft

这是一种通用技术。这些通常是帐号,它们出现在一个未整合的字段中,我需要确保它们与数据仓库在其ETL中使用的配置规则相匹配(当然,我假设它们在功能更强大的SSIS环境中使用)。 TrimStart)。
Cade Roux

Answers:


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

2
聪明,希望我能想到这一点。
Cade Roux

4
没关系,我意识到那个。不在子字符串中,因为它仅用于查找模式-比我想象的还要聪明。
Cade Roux

2
将其封装在函数中会导致查询速度变慢。我不太清楚为什么,但是我认为这与类型转换有关。内联使用SUBSTRING更快。
罗尼·欧弗比

1
问题指出了问题所在,当您解析零('0')时,会得到一个空白。您需要能够分辨出“ 0”值和空白值之间的差异。请参阅我的帖子以获取完整的解决方案:stackoverflow.com/a/21805081/555798
MikeTeeVee 2014年

1
@Arvo Wow ...一分钟我感到困惑,以为我回答了这个将帮助我的问题。第一次我Arvo在SO上看到了另一个!
Arvo Bowen

41

您为什么不只是将值转换为值INTEGER,然后再转换为VARCHAR

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

--------
       0

11
这是一个字符串列,所以我猜他们会不时期待非数字数据。类似于MRN编号,其中的数据大部分仅为数字。
乔尔·科恩荷恩

1
不幸的是,仅适用于数字数据,有时字符串也超出了整数的范围,因此您必须使用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

即使使用BIGINT,某些类型的字符串仍将失败此转换。考虑0001E123例如。
roaima

1
根据我的测试(和经验),与公认的答案相比,这是一个相对昂贵的操作。出于性能方面的考虑,如果可以的话,最好避免更改数据类型或比较不同类型的数据。
reedstonefood 2015年

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完全可以。

我的3rd-Party数据库存在相同的问题。
有了字母数字字段,许多人就没有前导空格了,,!
这使得在不清除丢失的前导零的情况下无法进行联接。

结论:

除了删除前导零外,您可能还想考虑在进行联接时仅使用前导零填充调整后的值。
更好的是,通过添加前导零来清理表中的数据,然后重建索引。
我认为这样会更快,更简单。

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 2014年

为什么会有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 2014年

2

如果string是数字,则cast(value int)将始终有效


这不能为问题提供答案。要批评或要求作者澄清,请在其帖子下方发表评论。- 评分
约瑟普·伊维奇

1
实际上这是一个答案,因为它确实有效吗?答案不必
太长

您是正确的,答案不一定要冗长,但是,如果可能的话,答案应该完整,而答案不是必须的。它更改结果的数据类型。我相信这会是一个更好的响应:SELECT CAST(CAST(value AS Int)AS VARCHAR)。您还应该提到的是,如果计算值超过2.1x10 ^ 9(八位数限制),则会收到Int错误。如果值超过19位(9.2x10 ^ 18),则使用BigInt会收到错误消息。
J.克里斯·康普顿

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

Thomas 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时出错”,则此操作将失败。
J.克里斯·康普顿

1

如果您使用的是Snowflake SQL,则可以使用以下命令:

ltrim(str_col,'0')

ltrim函数从左侧删除指定字符集的所有实例。

因此,'00000008A'上的ltrim(str_col,'0')将返回'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

试试这个:

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

0

如果您不想转换为int,我更喜欢下面的逻辑,因为它可以处理空值IFNULL(field,LTRIM(field,'0'))


0

在MySQL中,您可以执行此操作...

Trim(Leading '0' from your_column)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.