¿La forma más eficiente de T-SQL para rellenar un varchar a la izquierda a una cierta longitud?


205

En comparación con decir:

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

Retrocedí la última edición. La pregunta da una forma: estaba buscando formas más óptimas. La edición perdió esa implicación en busca de alguna otra cualidad.
Cade Roux

Respuestas:


323

Esto es simplemente un uso ineficiente de SQL, sin importar cómo lo haga.

tal vez algo como

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

donde X es su carácter de relleno y @n es el número de caracteres en la cadena resultante (suponiendo que necesita el relleno porque se trata de una longitud fija).

Pero como dije, realmente deberías evitar hacer esto en tu base de datos.


2
Hay momentos en los que se necesita ... por ejemplo, obtener un subconjunto de datos para una pantalla paginada.
Bip bip

77
+1 Acabo de probar una carga de diferentes métodos y este fue el más rápido. Sin RTRIM(@str)embargo, es posible que deba hacerlo si puede contener espacios finales.
Martin Smith

1
+1 Estaba desconcertando por qué mi char (6) no se rellenaba correctamente, y el RTRIM en la variable de entrada me ahorró algunos rasguños en la cabeza
jkelley

@ MartinSmith Gracias ... He estado tratando de entender por qué mi cadena no funcionaba y rtrim lo arregló. TY.
WernerCD

3
Esta es una respuesta genial. Por supuesto, debe evitar hacer esto cuando no sea necesario, pero a veces es inevitable; en mi caso, no tengo la opción de hacerlo en C # debido a restricciones de implementación, y alguien almacenó un número de franquicia como INT cuando debería haber sido una cadena numérica de 5 caracteres con ceros a la izquierda. Esto ayudó inmensamente.
Jim

57

Sé que esto se solicitó originalmente en 2008, pero hay algunas funciones nuevas que se introdujeron con SQL Server 2012. La función FORMAT simplifica muy bien el relleno dejado con ceros. También realizará la conversión por usted:

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

Actualizar:

Yo mismo quería probar la eficacia real de la función FORMATO. Me sorprendió bastante descubrir que la eficiencia no era muy buena en comparación con la respuesta original de AlexCuse . Aunque encuentro que la función FORMAT es más limpia, no es muy eficiente en términos de tiempo de ejecución. La tabla Tally que utilicé tiene 64,000 registros. Felicitaciones a Martin Smith por señalar la eficiencia del tiempo de ejecución.

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

Tiempos de ejecución de SQL Server: tiempo de CPU = 2157 ms, tiempo transcurrido = 2696 ms.

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

Tiempos de ejecución de SQL Server:

Tiempo de CPU = 31 ms, tiempo transcurrido = 235 ms.


1
Esto era exactamente lo que estaba buscando. Ayuda oficial para FORMAT: msdn.microsoft.com/es-MX/library/hh213505.aspx
Fer García

1
Esto se aplica a SQL Server 2014 en adelante de acuerdo con MSDN y mi propia experiencia de probarlo en SQL Server 2012.
arame3333

55
Sin embargo, esta no será la forma "más eficiente" como se le preguntó. En este ejemplo, el formato tarda 180 segundos frente a 12 segundos. stackoverflow.com/a/27447244/73226
Martin Smith

2
¡Puede ser "más eficiente" en términos del tiempo que le toma al programador usarlo!
underscore_d el

36

Varias personas dieron versiones de esto:

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

tenga cuidado con eso porque truncará sus datos reales si es más largo que n.


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

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

9

Tal vez una matanza excesiva tengo estos UDF para rellenar de izquierda a derecha

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

y a la derecha

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

El único problema con los UDF escalares es que funcionan mucho peor que el código en línea equivalente (además, hay problemas de tipo de datos). Esperemos que introduzcan un mejor rendimiento de UDF escalar y / o UDF escalares en línea en una versión futura.
Cade Roux

Si especifica una longitud menor que la longitud de var, estas funciones devuelven nulo. Envuelva cada una de las declaraciones replicadas con una declaración isnull para simplemente devolver var si la longitud es menor. isnull (replicate (...), '')
Jersey Dude

Agregar WITH SCHEMABINDING a la declaración de función mejorará la eficiencia de las funciones definidas por el usuario. Esto es algo que recomiendo agregar de forma predeterminada a todas sus funciones a menos que tenga una razón convincente para eliminarlo.
EricI

7

No estoy seguro de que el método que proporcione sea realmente ineficiente, pero una forma alternativa, siempre que no tenga que ser flexible en la longitud o el carácter de relleno, sería (suponiendo que quiera rellenarlo con " 0 "a 10 caracteres:

DECLARE
   @pad_characters VARCHAR(10)

SET @pad_characters = '0000000000'

SELECT RIGHT(@pad_characters + @str, 10)

2

probablemente exagerado, a menudo uso este 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 puedas hacer cosas como:

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

Esto se usa realmente en un udf que tiene que hacer algunas otras cosas para conformar algunos datos.
Cade Roux

Esto también funciona perfectamente si combina los tipos char y varchar.
Jimmy Baker

2

Me gustó la solución vnRocks, aquí está en forma de 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

Esta es una manera simple de rellenar a la izquierda:

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

Dónde xestá el número de pad y yel carácter del pad.

muestra:

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

Parece que estás hablando de números de relleno izquierdo de tal manera que se 1convierte 001.
Martin Smith


1

En SQL Server 2005 y versiones posteriores, podría crear una función CLR para hacer esto.


1

Qué tal esto:

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

3 es el número de zerospad


Esto me ayudó: CONCAT (REPLACE (SPACE (@n - LENGTH (@str)), '', '0'), @str)
ingham

1

Espero que esto ayude a alguien.

STUFF ( character_expression , start , length ,character_expression )

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

0

Yo uso este Le permite determinar la longitud que desea que tenga el resultado, así como un carácter de relleno predeterminado si no se proporciona uno. Por supuesto, puede personalizar la longitud de la entrada y la salida para los máximos que esté buscando.

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

Su experiencia puede ser diferente. :-)

Joey Morgan
Programador / Analista Director I
Unidad de Negocio de WellPoint Medicaid


0

Aquí está mi solución, que evita cadenas truncadas y usa SQL simple. Gracias a @AlexCuse , @Kevin y @Sklivvz , cuyas soluciones son la base de este 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;

0

Sé que esto no está agregando mucho a la conversación en este momento, pero estoy ejecutando un procedimiento de generación de archivos y va increíblemente lento. He estado usando replicar y vi este método de recorte y pensé que lo probaría.

Puede ver en mi código dónde el cambio entre los dos es adicional a la nueva variable @padding (y la limitación que ahora existe). Ejecuté mi procedimiento con la función en ambos estados con los mismos resultados en tiempo de ejecución. Entonces, al menos en SQLServer2016, no veo ninguna diferencia en la eficiencia que otros encontraron.

De todos modos, aquí está mi UDF que escribí hace años más los cambios de hoy, que es muy similar al de otro, ya que tiene una opción de parámetro IZQUIERDA / DERECHA y alguna comprobación de errores.

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

Tengo una función que lpad con x decimales: CREATE FUNCTION [dbo]. [LPAD_DEC] (- Agregue los parámetros para la función aquí @pad nvarchar (MAX), @string nvarchar (MAX), @length int, @dec int ) DEVOLUCIONES nvarchar (max) COMO COMIENZO - Declare aquí la variable de retorno 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

FINAL


-1

Para proporcionar valores numéricos redondeados a dos decimales pero rellenados a la derecha con ceros si es necesario, tengo:

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)

Si alguien puede pensar en una forma más ordenada, eso sería apreciado: lo anterior parece torpe .

Nota : en este caso, estoy usando SQL Server para enviar informes por correo electrónico en formato HTML y, por lo tanto, deseo formatear la información sin involucrar una herramienta adicional para analizar los datos.


1
No sabía que SQL Server le permitía declarar una variable sin especificar su tipo. De todos modos, su método parece "torpe" para uno que no funciona. :)
Andriy M

-4

Así es como normalmente rellenaría un varchar

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

14
Wow, esto es asombrosamente malo.
Hogan

1
Los bucles, cursores, etc. generalmente son malos en SQL. Podría estar bien en el código de la aplicación pero no en SQL. Algunas excepciones, pero esta no es una de ellas.
Davos el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.