A maneira mais eficiente de T-SQL para preencher um varchar à esquerda em um determinado comprimento?

205

Em comparação com dizer:

REPLICATE(@padchar, @len - LEN(@str)) + @str
Cade Roux
fonte
Revirei a última edição. A pergunta dá uma maneira - eu estava procurando maneiras mais ótimas. A edição perdeu essa implicação em busca de outra qualidade.
Cade Roux

Respostas:

323

Este é simplesmente um uso ineficiente do SQL, não importa como você o faça.

talvez algo como

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

onde X é seu caractere de preenchimento e @n é o número de caracteres na sequência resultante (supondo que você precise do preenchimento porque está lidando com um comprimento fixo).

Mas como eu disse, você realmente deve evitar fazer isso no seu banco de dados.

AlexCuse
fonte
2
Há momentos em que é necessário ... por exemplo, buscando um subconjunto de dados para uma tela paginada.
Beep beep
7
+1 Acabei de testar uma carga de métodos diferentes e este foi o mais rápido. Você pode precisar RTRIM(@str)se isso puder conter espaços à direita.
Martin Smith
1
+1 Foi intrigante sobre por que o meu char (6) não foi preenchimento corretamente, eo RTRIM na variável de entrada me salvou alguns coçar a cabeça
jkelley
@ MartinSmith Obrigado ... Estou tentando descobrir por que minha string não estava funcionando e a rtrim a corrigiu. TY.
WernerCD
3
Esta é uma ótima resposta. Claro que você deve evitar fazer isso quando não precisa, mas às vezes é inevitável; no meu caso, não tenho a opção de fazê-lo em C # devido a restrições de implantação, e alguém armazenou um número de franquia como INT quando deveria ter sido uma sequência numérica de 5 caracteres com zeros à esquerda. Isso ajudou imensamente.
Jim
57

Eu sei que isso foi solicitado originalmente em 2008, mas há algumas novas funções que foram introduzidas no SQL Server 2012. A função FORMAT simplifica o preenchimento deixado com zeros. Também realizará a conversão para você:

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

Atualizar:

Eu queria testar a eficiência real da função FORMAT. Fiquei bastante surpreso ao descobrir que a eficiência não era muito boa em comparação com a resposta original do AlexCuse . Embora eu ache a função FORMAT mais limpa, ela não é muito eficiente em termos de tempo de execução. A tabela Tally que usei tem 64.000 registros. Parabéns a Martin Smith por apontar a eficiência do tempo de execução.

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

Tempos de execução do SQL Server: tempo de CPU = 2157 ms, tempo decorrido = 2696 ms.

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

Tempos de execução do SQL Server:

Tempo de CPU = 31 ms, tempo decorrido = 235 ms.

jediCouncilor
fonte
1
Era exatamente isso que eu estava procurando. Ajuda oficial do FORMAT: msdn.microsoft.com/es-MX/library/hh213505.aspx
Fer García
1
Isso se aplica a SQL Server 2014, de acordo com MSDN e minha própria experiência de tentar-lo em SQL Server 2012.
arame3333
5
Esta não será a maneira "mais eficiente", conforme solicitado. Neste exemplo, o formato leva 180 segundos versus 12 segundos. stackoverflow.com/a/27447244/73226
Martin Smith
2
Pode ser "mais eficiente" em termos de tempo necessário para o programador utilizá-lo!
underscore_d
36

Várias pessoas deram versões disso:

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

tenha cuidado com isso, porque truncará seus dados reais se for maior que n.

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

SELECT RIGHT(@padstr + @str, @len)
Sklivvz
fonte
9

Talvez um extermínio eu tenha essas UDFs para cobrir esquerda e direita

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

e para a direita

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
TonyP
fonte
O único problema com UDFs escalares é que eles apresentam desempenho muito inferior ao código inline equivalente (além de haver problemas de tipo de dados). Esperamos que eles apresentem melhor desempenho UDF escalar e / ou UDF escalar embutido em uma versão futura.
Cade Roux
Se você especificar um comprimento menor que o comprimento de var, essas funções retornarão nulas. Quebra cada uma das instruções replicadas com uma instrução isnull para simplesmente retornar var se o comprimento for menor. isnull (réplica (...), '')
Jersey Cara
Adicionar WITH SCHEMABINDING à declaração da função melhorará a eficiência das funções definidas pelo usuário. Isso é algo que eu recomendo adicionar por padrão a todas as suas funções, a menos que você tenha um motivo convincente para removê-lo.
Erici
7

Não tenho certeza de que o método que você fornece seja realmente ineficiente, mas uma maneira alternativa, desde que não precise ser flexível no tamanho ou no caractere de preenchimento, seria (supondo que você queira preenchê-lo com " 0 "a 10 caracteres:

DECLARE
   @pad_characters VARCHAR(10)

SET @pad_characters = '0000000000'

SELECT RIGHT(@pad_characters + @str, 10)
Tom H
fonte
2

provavelmente exagere, costumo usar esse 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

Para que você possa fazer coisas como:

select dbo.f_pad_before('aaa', 10, '_')
ila
fonte
Na verdade, isso é usado em um udf que precisa fazer algumas outras coisas para conformar alguns dados.
Cade Roux
Isso também funciona perfeitamente se você estiver combinando os tipos char e varchar.
Jimmy Baker
2

Eu gostei da solução vnRocks, aqui está na forma de um 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
Kevin
fonte
2

Esta é uma maneira simples de colocar à esquerda:

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

Onde xestá o número do bloco e yo caractere do bloco.

amostra:

REPLACE(STR(FACT_HEAD.FACT_NO, 3, 0), ' ', 0)
Ahmad
fonte
Parece que você está falando sobre números de preenchimento à esquerda que 1se tornam 001.
Martin Smith
1
select right(replicate(@padchar, @len) + @str, @len)
Gordon Bell
fonte
1

No SQL Server 2005 e posterior, você pode criar uma função CLR para fazer isso.

Kramii
fonte
1

Que tal agora:

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

3 é o número de zerosa ser preenchido

Joshblair
fonte
Isso me ajudou: CONCAT (REPLACE (SPACE (@n - LENGTH (@str)), '', '0'), @str)
ingham
1

Espero que isso ajude alguém.

STUFF ( character_expression , start , length ,character_expression )

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

Eu uso este. Permite determinar o comprimento que você deseja que o resultado seja, além de um caractere de preenchimento padrão, se não houver um. É claro que você pode personalizar o tamanho da entrada e da saída para quaisquer valores máximos em que estiver se deparando.

/*===============================================================
 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

Sua milhagem pode variar. :-)

Joey Morgan
Programador / Analista Principal I
Unidade de Negócios WellPoint Medicaid

Joseph Morgan
fonte
0

Aqui está minha solução, que evita seqüências de caracteres truncadas e usa o SQL simples. Graças a @AlexCuse , @Kevin e @Sklivvz , cujas soluções são a base deste código.

 --[@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;
Net de ponto em massa
fonte
0

Eu sei que isso não está adicionando muito à conversa neste momento, mas estou executando um procedimento de geração de arquivos e está indo incrivelmente lento. Eu tenho usado replicate e vi esse método de apara e achei que eu daria uma chance.

Você pode ver no meu código onde a alternância entre os dois é uma adição à nova variável @padding (e a limitação que agora existe). Eu executei meu procedimento com a função nos dois estados com os mesmos resultados em tempo de execução. Portanto, pelo menos no SQLServer2016, não estou vendo nenhuma diferença de eficiência encontrada por outros.

De qualquer forma, aqui está a minha UDF que escrevi anos atrás, além das mudanças atuais, que são praticamente as mesmas que as de outras, exceto por ter uma opção de parâmetro ESQUERDA / DIREITA e alguma verificação de erro.

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
Skwirl cego
fonte
0

Eu tenho uma função que lpad com x decimais: CREATE FUNCTION [dbo]. [LPAD_DEC] (- Adicione os parâmetros para a função aqui @pad nvarchar (MAX), @ string nvarchar (MAX), @length int, @dec int ) RETORNA nvarchar (max) AS BEGIN - Declare a variável de retorno aqui 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

FIM

Pancho R
fonte
-1

Para fornecer valores numéricos arredondados para duas casas decimais, mas preenchidos à direita com zeros, se necessário, tenho:

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)

Se alguém puder pensar de uma maneira mais organizada, isso seria apreciado - o acima parece desajeitado .

Nota : neste caso, estou usando o SQL Server para enviar relatórios por email em formato HTML e, portanto, desejo formatar as informações sem envolver uma ferramenta adicional para analisar os dados.

mattpm
fonte
1
Não sabia que o SQL Server permitia declarar uma variável sem especificar seu tipo. De qualquer forma, seu método parece "desajeitado" para quem não trabalha. :)
Andriy M
-4

Aqui está como eu normalmente preencheria um varchar

WHILE Len(@String) < 8
BEGIN
    SELECT @String = '0' + @String
END
Deanos
fonte
14
Uau, isso é incrivelmente ruim.
Hogan
1
Loops, cursores, etc, geralmente são ruins no SQL. Pode ser bom no código do aplicativo, mas não no SQL. Algumas exceções, mas este não é um deles.
Davos