Le moyen T-SQL le plus efficace pour remplir un varchar à gauche à une certaine longueur?


205

Par rapport à dire:

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

J'ai annulé la dernière modification. La question donne un sens - je cherchais des moyens plus optimaux. Le montage a perdu cette implication à la recherche d'une autre qualité.
Cade Roux

Réponses:


323

Il s'agit simplement d'une utilisation inefficace de SQL, peu importe comment vous le faites.

peut-être quelque chose comme

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

où X est votre caractère de remplissage et @n est le nombre de caractères dans la chaîne résultante (en supposant que vous avez besoin du remplissage parce que vous avez affaire à une longueur fixe).

Mais comme je l'ai dit, vous devriez vraiment éviter de le faire dans votre base de données.


2
Il y a des moments où cela est nécessaire ... par exemple, récupérer un sous-ensemble de données pour un écran paginé.
Bip bip

7
+1 Je viens de tester une multitude de méthodes différentes et ce fut la plus rapide. Vous devrez peut-être le faire RTRIM(@str)si cela peut contenir des espaces de fin.
Martin Smith

1
+1 Je me
demandais

@MartinSmith Merci ... J'ai essayé de comprendre pourquoi ma chaîne ne fonctionnait pas et rtrim l'a corrigée. TY.
WernerCD

3
C'est une excellente réponse. Bien sûr, vous devriez éviter de faire cela lorsque vous n'êtes pas obligé, mais parfois c'est inévitable; dans mon cas, je n'ai pas le choix de le faire en C # en raison de contraintes de déploiement, et quelqu'un a stocké un numéro de franchise en tant qu'INT alors qu'il aurait dû être une chaîne numérique de 5 caractères avec des zéros en tête. Cela a énormément aidé.
Jim

57

Je sais que cela a été demandé à l'origine en 2008, mais de nouvelles fonctions ont été introduites avec SQL Server 2012. La fonction FORMAT simplifie bien le remplissage à gauche avec des zéros. Il effectuera également la conversion pour vous:

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

Mettre à jour:

Je voulais tester moi-même l'efficacité réelle de la fonction FORMAT. J'ai été assez surpris de constater que l'efficacité n'était pas très bonne par rapport à la réponse originale d' AlexCuse . Bien que je trouve la fonction FORMAT plus propre, elle n'est pas très efficace en termes de temps d'exécution. La table Tally que j'ai utilisée contient 64 000 enregistrements. Félicitations à Martin Smith pour avoir souligné l'efficacité du temps d'exécution.

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

Temps d'exécution SQL Server: temps CPU = 2157 ms, temps écoulé = 2696 ms.

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

Temps d'exécution de SQL Server:

Temps CPU = 31 ms, temps écoulé = 235 ms.


1
C'était exactement ce que je cherchais. Aide officielle pour FORMAT: msdn.microsoft.com/es-MX/library/hh213505.aspx
Fer García

1
Cela s'applique à SQL Server 2014 et suivants selon MSDN et ma propre expérience de l'essayer sur SQL Server 2012.
arame3333

5
Ce ne sera pas le moyen "le plus efficace" comme demandé. Dans cet exemple, le format prend 180 secondes contre 12 secondes. stackoverflow.com/a/27447244/73226
Martin Smith

2
Il peut être "plus efficace" en termes de temps mis par le programmeur pour l'utiliser!
underscore_d

36

Plusieurs personnes en ont donné des versions:

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

soyez prudent avec cela car il tronquera vos données réelles si elles sont plus longues que n.


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

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

9

Peut-être un sur-tuer j'ai ces UDF à remplir à gauche et à droite

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

et à droite

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

Le seul problème avec les FDU scalaires est qu'ils fonctionnent beaucoup moins bien que le code équivalent en ligne (en plus il y a des problèmes de type de données). Espérons qu'ils introduiront de meilleures performances UDF scalaires et / ou UDF scalaires en ligne dans une future version.
Cade Roux

Si vous spécifiez une longueur inférieure à la longueur de var, ces fonctions renvoient null. Enveloppez chacune des instructions répliquées avec une instruction isnull pour renvoyer simplement var si la longueur est inférieure. isnull (replicate (...), '')
Jersey Dude

L'ajout de WITH SCHEMABINDING à la déclaration de fonction améliorera l'efficacité des fonctions définies par l'utilisateur. C'est quelque chose que je recommande d'ajouter par défaut à toutes vos fonctions, sauf si vous avez une raison impérieuse de le supprimer.
EricI

7

Je ne suis pas sûr que la méthode que vous donnez soit vraiment inefficace, mais une autre manière, tant qu'elle ne doit pas être flexible dans la longueur ou le caractère de remplissage, serait (en supposant que vous vouliez la remplir avec " 0 "à 10 caractères:

DECLARE
   @pad_characters VARCHAR(10)

SET @pad_characters = '0000000000'

SELECT RIGHT(@pad_characters + @str, 10)

2

probablement exagéré, j'utilise souvent cet 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

Pour que vous puissiez faire des choses comme:

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

Ceci est en fait utilisé dans un udf qui doit faire quelques autres choses pour conformer certaines données.
Cade Roux

Cela fonctionne également parfaitement si vous combinez des types char et varchar.
Jimmy Baker

2

J'ai aimé la solution vnRocks, la voici sous la forme d'un 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

c'est un moyen simple de remplir à gauche:

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

xest le numéro du pad et yle caractère du pad.

échantillon:

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

Vous semblez parler de chiffres de remplissage gauche tels que cela 1devient 001.
Martin Smith


1

Dans SQL Server 2005 et versions ultérieures, vous pouvez créer une fonction CLR pour ce faire.


1

Que dis-tu de ça:

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

3 est le nombre de zerospad


Cela m'a aidé: CONCAT (REPLACE (SPACE (@n - LENGTH (@str)), '', '0'), @str)
ingham

1

J'espère que ça aidera quelqu'un.

STUFF ( character_expression , start , length ,character_expression )

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

0

J'utilise celui-ci. Il vous permet de déterminer la longueur souhaitée pour le résultat ainsi qu'un caractère de remplissage par défaut s'il n'est pas fourni. Bien sûr, vous pouvez personnaliser la longueur de l'entrée et de la sortie pour les maximums que vous rencontrez.

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

Votre kilométrage peut varier. :-)

Joey Morgan
Programmeur / Analyste Principal I
WellPoint Medicaid Business Unit


0

Voici ma solution, qui évite les chaînes tronquées et utilise du SQL simple. Merci à @AlexCuse , @Kevin et @Sklivvz , dont les solutions sont à la base de ce code.

 --[@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

Je sais que cela n'ajoute pas grand-chose à la conversation à ce stade, mais j'exécute une procédure de génération de fichiers et ça va incroyablement lentement. J'ai utilisé la réplique et j'ai vu cette méthode de coupe et je me suis dit que j'essaierais.

Vous pouvez voir dans mon code où le commutateur entre les deux s'ajoute à la nouvelle variable @padding (et la limitation qui existe maintenant). J'ai exécuté ma procédure avec la fonction dans les deux états avec les mêmes résultats en temps d'exécution. Donc, au moins dans SQLServer2016, je ne vois aucune différence d'efficacité que d'autres ont trouvée.

Quoi qu'il en soit, voici mon UDF que j'ai écrit il y a des années, plus les changements d'aujourd'hui, qui sont à peu près les mêmes que les autres, sauf qu'il dispose d'une option de paramètre LEFT / RIGHT et d'une vérification des erreurs.

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

J'ai une fonction qui lpad avec x décimales: CREATE FUNCTION [dbo]. [LPAD_DEC] (- Ajoutez les paramètres de la fonction ici @pad nvarchar (MAX), @string nvarchar (MAX), @length int, @dec int ) RETOURS nvarchar (max) AS BEGIN - Déclarez la variable de retour ici 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

FIN


-1

Pour fournir des valeurs numériques arrondies à deux décimales mais complétées à droite par des zéros si nécessaire, j'ai:

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 quelqu'un peut penser à une manière plus soignée, ce serait apprécié - ce qui précède semble maladroit .

Remarque : dans ce cas, j'utilise SQL Server pour envoyer des rapports par e-mail au format HTML et je souhaite donc formater les informations sans impliquer un outil supplémentaire pour analyser les données.


1
Je ne savais pas que SQL Server vous autorisait à déclarer une variable sans spécifier son type. Quoi qu'il en soit, votre méthode semble "maladroite" pour une méthode qui ne fonctionne pas. :)
Andriy M

-4

Voici comment je garnirais normalement un varchar

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

14
Wow, c'est incroyablement mauvais.
Hogan

1
Les boucles, curseurs, etc. sont généralement mauvais en SQL. Peut-être bien dans le code d'application mais pas dans SQL. Quelques exceptions mais ce n'est pas l'une d'elles.
Davos
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.