Il modo T-SQL più efficiente per riempire un varchar a sinistra per una certa lunghezza?


205

Rispetto a dire:

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

Ho eseguito il rollback dell'ultima modifica. La domanda offre un modo: stavo cercando modi più ottimali. La modifica ha perso quell'implicazione nella ricerca di qualche altra qualità.
Cade Roux,

Risposte:


323

Questo è semplicemente un uso inefficiente di SQL, indipendentemente da come lo si fa.

forse qualcosa del genere

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

dove X è il tuo carattere di riempimento e @n è il numero di caratteri nella stringa risultante (supponendo che tu abbia bisogno del riempimento perché hai a che fare con una lunghezza fissa).

Ma come ho detto, dovresti davvero evitare di farlo nel tuo database.


2
Ci sono momenti in cui è necessario ... ad esempio, recuperare un sottoinsieme di dati per una schermata di paginazione.
Bip beep

7
+1 Ho appena testato un sacco di metodi diversi e questo è stato il più veloce. Potrebbe essere necessario RTRIM(@str)però se questo può contenere spazi finali.
Martin Smith,

1
+1 Stavo sconcertando il motivo per cui il mio carattere (6) non stava riempiendo correttamente, e l'RTRIM sulla variabile di input mi ha salvato un po 'di
grattacapi

@MartinSmith Grazie ... Ho provato a capire perché la mia stringa non funzionava e rtrim l'ha riparata. TY.
WernerCD,

3
Questa è un'ottima risposta Ovviamente dovresti evitare di farlo quando non devi, ma a volte è inevitabile; nel mio caso non ho la scelta di farlo in C # a causa di vincoli di distribuzione e qualcuno ha memorizzato un numero di franchising come INT quando avrebbe dovuto essere una stringa numerica di 5 caratteri con zeri iniziali. Questo ha aiutato immensamente.
Jim,

57

So che questo è stato originariamente richiesto nel 2008, ma ci sono alcune nuove funzioni introdotte con SQL Server 2012. La funzione FORMAT semplifica il riempimento lasciato con zeri. Eseguirà anche la conversione per te:

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

Aggiornare:

Volevo testare personalmente l'effettiva efficienza della funzione FORMAT. Sono stato piuttosto sorpreso di scoprire che l'efficienza non era molto buona rispetto alla risposta originale di AlexCuse . Anche se trovo la funzione FORMAT più pulita, non è molto efficiente in termini di tempo di esecuzione. La tabella Tally che ho usato ha 64.000 record. Complimenti a Martin Smith per aver sottolineato l'efficienza dei tempi di esecuzione.

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

Tempi di esecuzione di SQL Server: tempo CPU = 2157 ms, tempo trascorso = 2696 ms.

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

Tempi di esecuzione di SQL Server:

Tempo CPU = 31 ms, tempo trascorso = 235 ms.


1
Questo era esattamente quello che stavo cercando. Guida ufficiale per FORMATO: msdn.microsoft.com/es-MX/library/hh213505.aspx
Fer García,

1
Questo vale per SQL Server 2014 in poi secondo MSDN e la mia esperienza personale nel provare su SQL Server 2012.
arame3333

5
Questo non sarà il modo "più efficiente" come richiesto. In questo esempio, il formato richiede 180 secondi contro 12 secondi. stackoverflow.com/a/27447244/73226
Martin Smith il

2
Potrebbe essere "più efficiente" in termini di tempo impiegato dal programmatore per sfruttarlo!
underscore_d

36

Diverse persone ne hanno fornito versioni:

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

fai attenzione perché troncerà i tuoi dati effettivi se è più lungo di n.


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

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

9

Forse un'uccisione eccessiva ho questi UDF da colpire a destra e a sinistra

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

ea destra

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

L'unico problema con gli UDF scalari è che si comportano in modo molto più scadente rispetto al codice in linea equivalente (inoltre ci sono problemi di tipo di dati). Speriamo che introducano migliori prestazioni UDF scalari e / o UDF scalari incorporati in una versione futura.
Cade Roux,

Se si specifica una lunghezza inferiore alla lunghezza di var, queste funzioni restituiscono null. Avvolgere ciascuna delle istruzioni replicate con un'istruzione isnull per restituire semplicemente var se la lunghezza è inferiore. isnull (replicate (...), '')
Jersey Dude

L'aggiunta di WITH SCHEMABINDING alla dichiarazione della funzione migliorerà l'efficienza delle funzioni definite dall'utente. Questa è una cosa che consiglio di aggiungere di default a tutte le tue funzioni a meno che tu non abbia un motivo convincente per rimuoverlo.
EricI

7

Non sono sicuro che il metodo che dai sia davvero inefficiente, ma un modo alternativo, purché non debba essere flessibile nella lunghezza o nel carattere di riempimento, sarebbe (supponendo che tu voglia riempirlo con " Da 0 "a 10 caratteri:

DECLARE
   @pad_characters VARCHAR(10)

SET @pad_characters = '0000000000'

SELECT RIGHT(@pad_characters + @str, 10)

2

probabilmente eccessivo, uso spesso questo 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

In modo che tu possa fare cose come:

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

Questo è effettivamente utilizzato in un udf che deve fare alcune altre cose per conformare alcuni dati.
Cade Roux,

Funziona perfettamente anche se stai combinando i tipi char e varchar.
Jimmy Baker,

2

Mi è piaciuta la soluzione vnRocks, qui ha la forma di 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

questo è un modo semplice per passare a sinistra:

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

Dov'è xil numero del pad ed yè il carattere del pad.

campione:

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

Sembra che tu stia parlando di numeri di imbottitura a sinistra tali che 1diventano 001.
Martin Smith,


1

In SQL Server 2005 e versioni successive è possibile creare una funzione CLR per eseguire questa operazione.


1

Cosa ne pensi di questo:

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

3 è il numero di zerospad


Questo mi ha aiutato: CONCAT (REPLACE (SPACE (@n - LENGTH (@str)), '', '0'), @str)
ingham,

1

Spero che questo aiuti qualcuno.

STUFF ( character_expression , start , length ,character_expression )

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

0

Io uso questo. Consente di determinare la lunghezza desiderata per il risultato e un carattere di riempimento predefinito se non viene fornito. Ovviamente puoi personalizzare la lunghezza dell'input e dell'output per qualunque sia il tuo massimo.

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

Il tuo chilometraggio può variare. :-)

Joey Morgan
Programmatore / Analista Principale I
WellPoint Business Unit Medicaid


0

Ecco la mia soluzione, che evita le stringhe troncate e utilizza semplicemente il vecchio SQL. Grazie a @AlexCuse , @Kevin e @Sklivvz , le cui soluzioni sono alla base di questo codice.

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

So che questo non aggiunge molto alla conversazione a questo punto, ma sto eseguendo una procedura di generazione di file e sta andando incredibilmente lentamente. Ho usato replicare e ho visto questo metodo di taglio e ho pensato di provarlo.

Nel mio codice puoi vedere dove si trova il passaggio tra i due oltre alla nuova variabile @padding (e la limitazione che ora esiste). Ho eseguito la mia procedura con la funzione in entrambi gli stati con gli stessi risultati in fase di esecuzione. Quindi, almeno in SQLServer2016, non vedo alcuna differenza di efficienza che altri hanno riscontrato.

Ad ogni modo, ecco il mio UDF che ho scritto anni fa più le modifiche oggi, che è molto simile a quello degli altri, poiché ha un'opzione param SINISTRA / DESTRA e qualche controllo degli errori.

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

Ho una funzione che lpad con decimali x: CREATE FUNCTION [dbo]. [LPAD_DEC] (- Aggiungi qui i parametri della funzione @pad nvarchar (MAX), @string nvarchar (MAX), @length int, @dec int ) RESI nvarchar (max) COME INIZIO - Dichiarare qui la variabile di ritorno 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

FINE


-1

Per fornire valori numerici arrotondati al secondo decimale ma riempiti a destra con zeri, se necessario, ho:

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 qualcuno potesse pensare a un modo più ordinato, questo sarebbe apprezzato - quanto sopra sembra goffo .

Nota : in questo caso, sto usando SQL Server per inviare report via email in formato HTML e quindi desidero formattare le informazioni senza coinvolgere uno strumento aggiuntivo per analizzare i dati.


1
Non sapevo che SQL Server ti consentisse di dichiarare una variabile senza specificarne il tipo. Ad ogni modo, il tuo metodo sembra "goffo" per uno non funzionante. :)
Andriy M

-4

Ecco come normalmente riempirei un varchar

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

14
Wow, questo è incredibilmente male.
Hogan,

1
Cicli, cursori, ecc. Sono generalmente tutti cattivi in ​​SQL. Potrebbe andare bene nel codice dell'applicazione ma non in SQL. Alcune eccezioni, ma questa non è una di queste.
Davos,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.