Najbardziej efektywny sposób T-SQL na wstawienie varchara po lewej do pewnej długości?


205

W porównaniu do powiedzenia:

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

Cofnąłem ostatnią edycję. Pytanie daje jeden sposób - szukałem bardziej optymalnych sposobów. Edycja straciła tę implikację w poszukiwaniu innej jakości.
Cade Roux,

Odpowiedzi:


323

Jest to po prostu nieefektywne użycie SQL, bez względu na to, jak to robisz.

może coś takiego

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

gdzie X jest znakiem dopełniania, a @n jest liczbą znaków w wynikowym ciągu (zakładając, że potrzebujesz dopełnienia, ponieważ masz do czynienia ze stałą długością).

Ale jak powiedziałem, naprawdę powinieneś unikać robienia tego w swojej bazie danych.


2
Są chwile, kiedy jest to potrzebne ... na przykład pobieranie podzbioru danych dla ekranu stronicowanego.
Sygnał dźwiękowy

7
+1 Właśnie przetestowałem wiele różnych metod i było to najszybsze. Może być konieczne, RTRIM(@str)jeśli może zawierać końcowe spacje.
Martin Smith

1
+1 Zastanawiałem się, dlaczego mój char (6) nie wypełniał poprawnie, a RTRIM na zmiennej wejściowej zaoszczędził mi drapania głowy
jkelley

@MartinSmith Dziękuję ... Próbowałem dowiedzieć się, dlaczego mój ciąg nie działa i rtrim to naprawił. TY.
WernerCD

3
To świetna odpowiedź. Oczywiście powinieneś unikać robienia tego, kiedy nie musisz, ale czasem jest to nieuniknione; w moim przypadku nie mam wyboru, aby to zrobić w języku C # z powodu ograniczeń związanych z wdrożeniem, a ktoś zapisał numer franczyzy jako INT, gdy powinien to być ciąg 5-znakowy z wiodącymi zerami. To ogromnie pomogło.
Jim

57

Wiem, że pierwotnie o to pytano w 2008 r., Ale w SQL Server 2012 wprowadzono kilka nowych funkcji. Funkcja FORMAT ładnie upraszcza wypełnianie zer zerami. Przeprowadzi również konwersję dla Ciebie:

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

Aktualizacja:

Chciałem sam przetestować rzeczywistą wydajność funkcji FORMAT. Byłem dość zaskoczony, gdy stwierdziłem, że wydajność nie była bardzo dobra w porównaniu z pierwotną odpowiedzią AlexCuse . Chociaż uważam, że funkcja FORMAT jest czystsza, nie jest ona zbyt wydajna pod względem czasu wykonania. Użyta przeze mnie tabela Tally ma 64 000 rekordów. Wyrazy uznania dla Martina Smitha za wskazanie wydajności czasu wykonania.

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

Czasy wykonania programu SQL Server: czas procesora = 2157 ms, czas, który upłynął = 2696 ms.

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

Czasy wykonania programu SQL Server:

Czas procesora = 31 ms, czas, który upłynął = 235 ms.


1
Właśnie tego szukałem. Oficjalna pomoc dla FORMATU: msdn.microsoft.com/es-MX/library/hh213505.aspx
Fer García

1
Odnosi się to do SQL Server 2014 według MSDN i własnego doświadczenia z wypróbowaniem go na SQL Server 2012.
arame3333

5
Nie będzie to jednak „najbardziej wydajny” sposób, o jaki prosi się. W tym przykładzie Format zajmuje 180 sekund vs 12 sekund. stackoverflow.com/a/27447244/73226
Martin Smith

2
Może być „najbardziej wydajny” pod względem czasu, jaki programiści wykorzystują!
underscore_d

36

Kilka osób podało wersje tego:

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

bądź ostrożny, ponieważ spowoduje to obcięcie twoich danych, jeśli jest on dłuższy niż n.


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

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

9

Być może nadmierne zabójstwo mam te UDF-y, które można przewrócić w lewo i w prawo

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

i na prawo

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

Jedynym problemem związanym z skalarnymi UDF jest to, że działają one znacznie słabiej niż równoważny kod wbudowany (plus występują problemy z typem danych). Mamy nadzieję, że wprowadzą lepszą skalarną wydajność UDF i / lub wbudowane skalarne funkcje UDF w przyszłej wersji.
Cade Roux,

Jeśli określisz długość mniejszą niż długość var, wówczas te funkcje zwracają null. Zawiń każdą z replikowanych instrukcji instrukcją isnull, aby po prostu zwrócić var, jeśli długość jest mniejsza. isnull (replicate (...), '')
Jersey Dude

Dodanie Z SCHEMABINDINGIEM do deklaracji funkcji poprawi wydajność funkcji zdefiniowanych przez użytkownika. Jest to coś, co zalecam domyślnie dodawać do wszystkich swoich funkcji, chyba że masz ważny powód, aby je usunąć.
Eric,

7

Nie jestem pewien, czy podana metoda jest naprawdę nieefektywna, ale będzie to alternatywny sposób, o ile nie musi ona być elastyczna pod względem długości lub znaku dopełniania (zakładając, że chcesz ją uzupełnić za pomocą „ 0 "do 10 znaków:

DECLARE
   @pad_characters VARCHAR(10)

SET @pad_characters = '0000000000'

SELECT RIGHT(@pad_characters + @str, 10)

2

prawdopodobnie przesada, często używam tego 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

Abyś mógł robić takie rzeczy jak:

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

Jest to faktycznie używane w udf, który musi zrobić kilka innych rzeczy, aby dostosować niektóre dane.
Cade Roux,

Działa to również doskonale, jeśli łączysz typy char i varchar.
Jimmy Baker

2

Podobało mi się rozwiązanie vnRocks, tutaj jest w formie 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

jest to prosty sposób na wstawienie lewej strony:

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

Gdzie xjest numer pada i yznak pada.

próba:

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

Wydaje się, że mówisz o lewych liczbach dopełniających, które się 1stają 001.
Martin Smith,


1

W SQL Server 2005 i nowszych wersjach można utworzyć funkcję CLR, aby to zrobić.


1

Co powiesz na to:

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

3 to liczba zerospad


Pomogło mi to: CONCAT (REPLACE (SPACE (@n - LENGTH (@str)), '', '0'), @str)
ingham

1

Mam nadzieję, że to komuś pomoże.

STUFF ( character_expression , start , length ,character_expression )

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

0

Używam tego. Pozwala określić długość, która ma być wynikiem, a także domyślny znak dopełniający, jeśli nie zostanie podany. Oczywiście możesz dostosować długość danych wejściowych i wyjściowych dla dowolnych maksymalnych poziomów.

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

Twój przebieg może się różnić. :-)

Joey Morgan
programista / główny analityk I
WellPoint Medicaid Business Unit


0

Oto moje rozwiązanie, które pozwala uniknąć skróconych ciągów znaków i używa zwykłego ol 'SQL. Dzięki @AlexCuse , @Kevin i @Sklivvz , których rozwiązania są podstawą tego kodu.

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

Wiem, że w tej chwili nie ma to większego znaczenia w rozmowie, ale uruchamiam procedurę generowania plików, która idzie niesamowicie wolno. Użyłem replikacji i widziałem tę metodę przycinania i pomyślałem, że dam jej szansę.

W moim kodzie widać, że przełączanie między nimi jest dodatkiem do nowej zmiennej @padding (i istniejącego ograniczenia). Uruchomiłem procedurę z funkcją w obu stanach z tymi samymi wynikami w czasie wykonania. Tak więc przynajmniej w SQLServer2016 nie widzę żadnej różnicy w wydajności, którą inni stwierdzili.

Tak czy inaczej, oto mój UDF, który napisałem lata temu, oraz dzisiejsze zmiany, które są w dużej mierze takie same jak inne, oprócz tego, że ma opcję LEWEGO / PRAWEGO parametru i sprawdzanie błędów.

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

Mam jedną funkcję, która lpad z dziesiętnymi x: UTWÓRZ FUNKCJĘ [dbo]. [LPAD_DEC] (- Dodaj parametry funkcji tutaj @pad nvarchar (MAX), @string nvarchar (MAX), @length int, @dec int ) ZWRACA nvarchar (max) AS BEGIN - Deklaruj tutaj zmienną zwrotną DEKLARUJ @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

KONIEC


-1

Aby podać wartości liczbowe zaokrąglone do dwóch miejsc po przecinku, ale w razie potrzeby uzupełnione zerami, mam:

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)

Byłoby to mile widziane, jeśli ktoś mógłby wymyślić lepszy sposób - powyższe wydaje się niezdarne .

Uwaga : w tym przypadku używam programu SQL Server do wysyłania raportów pocztą e-mail w formacie HTML, dlatego chcę sformatować informacje bez udziału dodatkowego narzędzia do analizy danych.


1
Nie wiedziałem, że SQL Server pozwala zadeklarować zmienną bez określania jej typu. W każdym razie twoja metoda wydaje się „niezdarna” dla niedziałającej. :)
Andriy M

-4

Oto jak normalnie bym wypełnił varchara

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

14
Wow, to jest niesamowicie złe.
Hogan,

1
Pętle, kursory itp. Są ogólnie złe w SQL. Może być dobrze w kodzie aplikacji, ale nie w SQL. Niektóre wyjątki, ale to nie jest jeden z nich.
Davos,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.