将左侧的varchar填充到一定长度的最有效的T-SQL方法?


205

相比说:

REPLICATE(@padchar, @len - LEN(@str)) + @str

我回滚了上一次编辑。这个问题给出了一种方法-我一直在寻找更优化的方法。在寻找其他质量时,编辑失去了这种含义。
Cade Roux 2012年

Answers:


323

无论您怎么做,这都是对SQL的低效使用。

也许像

right('XXXXXXXXXXXX'+ rtrim(@str), @n)

其中X是您的填充字符,@n是结果字符串中的字符数(假设您需要填充,因为您要处理的是固定长度)。

但是正如我所说,您应该避免在数据库中这样做。


2
有时需要它……例如,获取页面屏幕的数据子集。
哔哔哔声,2010年

7
+1刚刚测试了多种方法,这是最快的。如果可能RTRIM(@str)包含尾随空格,则可能需要。
马丁·史密斯

1
+1令人困惑,为什么我的char(6)不能正确填充,而输入变量上的RTRIM为我省去了一些挠头
jkelley 2011年

@MartinSmith谢谢...我一直在试图弄清为什么我的弦无法正常工作,而rtrim修复了它。TY。
WernerCD

3
这是一个很好的答案。当然,在不需要时应避免这样做,但有时这是不可避免的。在我的情况下,由于部署限制,我没有选择在C#中执行此操作,并且当应以前导零开头的5个字符的数字字符串时,有人将特许经营号存储为INT。这极大地帮助了。
2014年

57

我知道最初是在2008年问这个问题的,但是SQL Server 2012中引入了一些新函数。FORMAT函数很好地简化了零填充。它还将为您执行转换:

declare @n as int = 2
select FORMAT(@n, 'd10') as padWithZeros

更新:

我想亲自测试FORMAT函数的实际效率。我发现与AlexCuse最初的回答相比效率不是很好,感到非常惊讶。尽管我发现FORMAT函数更清洁,但是在执行时间方面并不是很有效。我使用的Tally表有64,000条记录。荣誉对马丁·史密斯的指出了执行时间效率。

SET STATISTICS TIME ON
select FORMAT(N, 'd10') as padWithZeros from Tally
SET STATISTICS TIME OFF

SQL Server执行时间:CPU时间= 2157毫秒,经过的时间= 2696毫秒。

SET STATISTICS TIME ON
select right('0000000000'+ rtrim(cast(N as varchar(5))), 10) from Tally
SET STATISTICS TIME OFF

SQL Server执行时间:

CPU时间= 31毫秒,经过时间= 235毫秒。


1
这正是我想要的。格式的官方帮助:msdn.microsoft.com/es-MX/library/hh213505.aspx
FerGarcía

1
这适用于SQL Server 2014年起根据MSDN和我自己的努力是对SQL Server 2012的经验
arame3333

5
但这并不是要求的“最有效”的方式。在此示例中,格式需要180秒与12秒。stackoverflow.com/a/27447244/73226
马丁·史密斯

2
就程序员利用它而言,这可能是“最有效的”!
underscore_d

36

有几个人给出了这样的版本:

right('XXXXXXXXXXXX'+ @str, @n)

请注意这一点,因为如果长度大于n,它将截断您的实际数据。


17
@padstr = REPLICATE(@padchar, @len) -- this can be cached, done only once

SELECT RIGHT(@padstr + @str, @len)

9

也许过度杀了我让这些UDF左右移动

ALTER   Function [dbo].[fsPadLeft](@var varchar(200),@padChar char(1)='0',@len int)
returns varchar(300)
as
Begin

return replicate(@PadChar,@len-Len(@var))+@var

end

并向右

ALTER function [dbo].[fsPadRight](@var varchar(200),@padchar char(1)='0', @len int) returns varchar(201) as
Begin

--select @padChar=' ',@len=200,@var='hello'


return  @var+replicate(@PadChar,@len-Len(@var))
end

标量UDF的唯一问题是,它们的性能要比等效的内联代码差得多(此外还有数据类型问题)。希望他们在将来的版本中引入更好的标量UDF性能和/或内联标量UDF。
Cade Roux 2010年

如果指定的长度小于var的长度,则这些函数将返回null。如果长度较小,则将每个复制语句与一个isnull语句包装在一起,以简单地返回var。notull(replicate(...),'')
泽西·杜德

在函数声明中添加WITH SCHEMABINDING将提高用户定义函数的效率。我建议您默认将其添加到所有功能中,除非您有令人信服的理由将其删除。
埃里克

7

我不确定您提供的方法是否真的没有效率,但是可以选择一种替代方法,只要该方法不必在长度或填充字符方面保持灵活性即可(假设您想使用“ 0“到10个字符:

DECLARE
   @pad_characters VARCHAR(10)

SET @pad_characters = '0000000000'

SELECT RIGHT(@pad_characters + @str, 10)

2

可能是矫kill过正,我经常使用此UDF:

CREATE FUNCTION [dbo].[f_pad_before](@string VARCHAR(255), @desired_length INTEGER, @pad_character CHAR(1))
RETURNS VARCHAR(255) AS  
BEGIN

-- Prefix the required number of spaces to bulk up the string and then replace the spaces with the desired character
 RETURN ltrim(rtrim(
        CASE
          WHEN LEN(@string) < @desired_length
            THEN REPLACE(SPACE(@desired_length - LEN(@string)), ' ', @pad_character) + @string
          ELSE @string
        END
        ))
END

这样您就可以执行以下操作:

select dbo.f_pad_before('aaa', 10, '_')

实际上,这是在udf中使用的,而udf必须做一些其他事情来符合某些数据。
Cade Roux

如果您将char和varchar类型组合在一起,这也可以完美地工作。
吉米·贝克

2

我喜欢vnRocks解决方案,这里是udf的形式

create function PadLeft(
      @String varchar(8000)
     ,@NumChars int
     ,@PadChar char(1) = ' ')
returns varchar(8000)
as
begin
    return stuff(@String, 1, 0, replicate(@PadChar, @NumChars - len(@String)))
end

2

这是向左滑动的一种简单方法:

REPLACE(STR(FACT_HEAD.FACT_NO, x, 0), ' ', y)

x填充编号在哪里y,填充字符在哪里。

样品:

REPLACE(STR(FACT_HEAD.FACT_NO, 3, 0), ' ', 0)

您似乎在谈论左填充数字,使1成为001
马丁·史密斯


1

在SQL Server 2005及更高版本中,您可以创建CLR函数来执行此操作。


1

这个怎么样:

replace((space(3 - len(MyField))

3是zeros要垫的数量


这对我有帮助:CONCAT(REPLACE(SPACE(@n-LENGTH(@str)),'','0'),@str)
ingham 2013年

1

我希望这可以帮助别人。

STUFF ( character_expression , start , length ,character_expression )

select stuff(@str, 1, 0, replicate('0', @n - len(@str)))

0

我用这个。它允许您确定所需结果的长度以及默认的填充字符(如果未提供)。当然,您可以自定义输入和输出的长度,以适应您遇到的任何最大值。

/*===============================================================
 Author         : Joey Morgan
 Create date    : November 1, 2012
 Description    : Pads the string @MyStr with the character in 
                : @PadChar so all results have the same length
 ================================================================*/
 CREATE FUNCTION [dbo].[svfn_AMS_PAD_STRING]
        (
         @MyStr VARCHAR(25),
         @LENGTH INT,
         @PadChar CHAR(1) = NULL
        )
RETURNS VARCHAR(25)
 AS 
      BEGIN
        SET @PadChar = ISNULL(@PadChar, '0');
        DECLARE @Result VARCHAR(25);
        SELECT
            @Result = RIGHT(SUBSTRING(REPLICATE('0', @LENGTH), 1,
                                      (@LENGTH + 1) - LEN(RTRIM(@MyStr)))
                            + RTRIM(@MyStr), @LENGTH)

        RETURN @Result

      END

你的旅费可能会改变。:-)

Joey Morgan
I
WellPoint Medicaid业务部程序员兼分析师


0

这是我的解决方案,它避免了字符串被截断并使用普通的SQL。感谢@AlexCuse@Kevin@Sklivvz,它们的解决方案是此代码的基础。

 --[@charToPadStringWith] is the character you want to pad the string with.
declare @charToPadStringWith char(1) = 'X';

-- Generate a table of values to test with.
declare @stringValues table (RowId int IDENTITY(1,1) NOT NULL PRIMARY KEY, StringValue varchar(max) NULL);
insert into @stringValues (StringValue) values (null), (''), ('_'), ('A'), ('ABCDE'), ('1234567890');

-- Generate a table to store testing results in.
declare @testingResults table (RowId int IDENTITY(1,1) NOT NULL PRIMARY KEY, StringValue varchar(max) NULL, PaddedStringValue varchar(max) NULL);

-- Get the length of the longest string, then pad all strings based on that length.
declare @maxLengthOfPaddedString int = (select MAX(LEN(StringValue)) from @stringValues);
declare @longestStringValue varchar(max) = (select top(1) StringValue from @stringValues where LEN(StringValue) = @maxLengthOfPaddedString);
select [@longestStringValue]=@longestStringValue, [@maxLengthOfPaddedString]=@maxLengthOfPaddedString;

-- Loop through each of the test string values, apply padding to it, and store the results in [@testingResults].
while (1=1)
begin
    declare
        @stringValueRowId int,
        @stringValue varchar(max);

    -- Get the next row in the [@stringLengths] table.
    select top(1) @stringValueRowId = RowId, @stringValue = StringValue
    from @stringValues 
    where RowId > isnull(@stringValueRowId, 0) 
    order by RowId;

    if (@@ROWCOUNT = 0) 
        break;

    -- Here is where the padding magic happens.
    declare @paddedStringValue varchar(max) = RIGHT(REPLICATE(@charToPadStringWith, @maxLengthOfPaddedString) + @stringValue, @maxLengthOfPaddedString);

    -- Added to the list of results.
    insert into @testingResults (StringValue, PaddedStringValue) values (@stringValue, @paddedStringValue);
end

-- Get all of the testing results.
select * from @testingResults;

0

我知道目前这并没有增加多少对话,但是我正在运行一个文件生成过程,其运行速度非常慢。我一直在使用复制,并看到了这种修整方法,然后想出了办法。

您可以在我的代码中看到除了新的@padding变量(以及现在存在的限制)之外,两者之间的切换在哪里。我在两种状态下都使用该函数运行了过程,并且执行时间相同。因此,至少在SQLServer2016中,我没有看到其他人发现的效率差异。

无论如何,这是我几年前写的UDF,加上今天的更改,与其他更改相同,除了具有LEFT / RIGHT参数选项和一些错误检查。

CREATE FUNCTION PadStringTrim 
(
    @inputStr varchar(500), 
    @finalLength int, 
    @padChar varchar (1),
    @padSide varchar(1)
)
RETURNS VARCHAR(500)

AS BEGIN
    -- the point of this function is to avoid using replicate which is extremely slow in SQL Server
    -- to get away from this though we now have a limitation of how much padding we can add, so I've settled on a hundred character pad 
    DECLARE @padding VARCHAR (100) = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
    SET @padding = REPLACE(@padding, 'X', @padChar)


    SET @inputStr = RTRIM(LTRIM(@inputStr))

    IF LEN(@inputStr) > @finalLength 
        RETURN '!ERROR!' -- can search for ! in the returned text 

    ELSE IF(@finalLength > LEN(@inputStr))
        IF @padSide = 'L'
            SET @inputStr = RIGHT(@padding + @inputStr, @finalLength)
            --SET @inputStr = REPLICATE(@padChar, @finalLength - LEN(@inputStr)) + @inputStr
        ELSE IF @padSide = 'R'
            SET @inputStr = LEFT(@inputStr + @padding, @finalLength)
            --SET @inputStr = @inputStr + REPLICATE(@padChar, @finalLength - LEN(@inputStr)) 



    -- if LEN(@inputStr) = @finalLength we just return it 
    RETURN @inputStr;
END

-- SELECT  dbo.PadStringTrim( tblAccounts.account, 20, '~' , 'R' ) from tblAccounts
-- SELECT  dbo.PadStringTrim( tblAccounts.account, 20, '~' , 'L' ) from tblAccounts

0

我有一个用x小数点表示lpad的函数:CREATE FUNCTION [dbo]。[LPAD_DEC](-在此处添加函数的参数@pad nvarchar(MAX),@ string nvarchar(MAX),@ length int,@ dec int )从头开始返回nvarchar(max)-在此处声明返回变量DECLARE @resp nvarchar(max)

IF LEN(@string)=@length
BEGIN
    IF CHARINDEX('.',@string)>0
    BEGIN
        SELECT @resp = CASE SIGN(@string)
            WHEN -1 THEN
                -- Nros negativos grandes con decimales
                concat('-',SUBSTRING(replicate(@pad,@length),1,@length-len(@string)),ltrim(str(abs(@string),@length,@dec)))
            ELSE
                -- Nros positivos grandes con decimales
                concat(SUBSTRING(replicate(@pad,@length),1,@length-len(@string)),ltrim(str(@string,@length,@dec)))                  
            END
    END
    ELSE
    BEGIN
        SELECT @resp = CASE SIGN(@string)
            WHEN -1 THEN
                --Nros negativo grande sin decimales
                concat('-',SUBSTRING(replicate(@pad,@length),1,(@length-3)-len(@string)),ltrim(str(abs(@string),@length,@dec)))
            ELSE
                -- Nros positivos grandes con decimales
                concat(SUBSTRING(replicate(@pad,@length),1,@length-len(@string)),ltrim(str(@string,@length,@dec)))                  
            END                     
    END
END
ELSE
    IF CHARINDEX('.',@string)>0
    BEGIN
        SELECT @resp =CASE SIGN(@string)
            WHEN -1 THEN
                -- Nros negativos con decimales
                concat('-',SUBSTRING(replicate(@pad,@length),1,@length-len(@string)),ltrim(str(abs(@string),@length,@dec)))
            ELSE
                --Ntos positivos con decimales
                concat(SUBSTRING(replicate(@pad,@length),1,@length-len(@string)),ltrim(str(abs(@string),@length,@dec))) 
            END
    END
    ELSE
    BEGIN
        SELECT @resp = CASE SIGN(@string)
            WHEN -1 THEN
                -- Nros Negativos sin decimales
                concat('-',SUBSTRING(replicate(@pad,@length-3),1,(@length-3)-len(@string)),ltrim(str(abs(@string),@length,@dec)))
            ELSE
                -- Nros Positivos sin decimales
                concat(SUBSTRING(replicate(@pad,@length),1,(@length-3)-len(@string)),ltrim(str(abs(@string),@length,@dec)))
            END
    END
RETURN @resp

结束


-1

要提供四舍五入到小数点后两位的数值,但如果需要,可以用零右填充我有:

DECLARE @value = 20.1
SET @value = ROUND(@value,2) * 100
PRINT LEFT(CAST(@value AS VARCHAR(20)), LEN(@value)-2) + '.' + RIGHT(CAST(@value AS VARCHAR(20)),2)

如果有人能想到一种更整洁的方式,那将不胜感激-上面的内容似乎很笨拙

注意:在这种情况下,我正在使用SQL Server通过HTML格式通过电子邮件发送报告,因此希望在不使用其他工具来解析数据的情况下格式化信息。


1
不知道SQL Server允许您在不指定变量类型的情况下声明变量。无论如何,对于不起作用的方法,您的方法似乎“笨拙”。:)
Andriy M 2012年

-4

这是我通常填充varchar的方式

WHILE Len(@String) < 8
BEGIN
    SELECT @String = '0' + @String
END

14
哇,这真是太糟糕了。
霍根2012年

1
在SQL中,循环,游标等通常都是不好的。在应用程序代码中可能没问题,但在SQL中可能没问题。一些例外,但这不是其中之一。
达沃斯2012年
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.