Самый эффективный способ T-SQL для заполнения varchar слева до определенной длины?

205

По сравнению с сказать:

REPLICATE(@padchar, @len - LEN(@str)) + @str
Кейд Ру
источник
Я откатил последнее редактирование. Вопрос дает один путь - я искал более оптимальные пути. Редактирование потеряло этот смысл в поисках какого-то другого качества.
Cade Roux

Ответы:

323

Это просто неэффективное использование SQL, независимо от того, как вы это делаете.

возможно что-то вроде

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

где X - ваш символ заполнения, а @n - количество символов в результирующей строке (при условии, что вам нужно заполнение, потому что вы имеете дело с фиксированной длиной).

Но, как я уже сказал, вы должны избегать этого в своей базе данных.

AlexCuse
источник
2
Есть моменты, когда это необходимо ... например, выборка подмножества данных для выгружаемого экрана.
Звуковой сигнал
7
+1 Только что проверил кучу разных методов и это было быстрее всего. Возможно, вам потребуется, RTRIM(@str)если это может содержать конечные пробелы.
Мартин Смит
1
+1 Я ломал голову над тем, почему мой char (6) не дополнял должным образом, а RTRIM на входной переменной спасла меня от царапин на голове
jkelley
@MartinSmith Спасибо ... Я пытался выяснить, почему моя строка не работала, и Ртрим исправил ее. Т.Ю..
WernerCD
3
Это отличный ответ. Конечно, вы должны избегать этого, когда это не нужно, но иногда это неизбежно; в моем случае у меня нет выбора сделать это в C # из-за ограничений развертывания, и кто-то сохранил номер франшизы как INT, когда это должна была быть 5-символьная числовая строка с начальными нулями. Это очень помогло.
Джим
57

Я знаю, что об этом изначально спрашивали еще в 2008 году, но в SQL Server 2012 были введены некоторые новые функции. Функция FORMAT значительно упрощает заполнение нулями. Он также выполнит преобразование для вас:

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

Обновить:

Я хотел проверить фактическую эффективность функции FORMAT самостоятельно. Я был очень удивлен, обнаружив, что эффективность была не очень хорошей по сравнению с первоначальным ответом от AlexCuse . Хотя я считаю, что функция FORMAT более чистая, она не очень эффективна с точки зрения времени выполнения. Таблица Tally, которую я использовал, насчитывает 64 000 записей. Престижность Мартину Смиту за указание эффективности времени выполнения.

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

Время выполнения SQL Server: время ЦП = 2157 мс, прошедшее время = 2696 мс.

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

Время выполнения SQL Server:

Время процессора = 31 мс, прошедшее время = 235 мс.

jediCouncilor
источник
1
Это было именно то, что я искал. Официальная помощь для FORMAT: msdn.microsoft.com/es-MX/library/hh213505.aspx
Фер Гарсия
1
Это относится к SQL Server 2014 и более
поздним версиям в
5
Это будет не самый эффективный способ, как просили. В этом примере формат занимает 180 секунд против 12 секунд. stackoverflow.com/a/27447244/73226
Мартин Смит
2
Это может быть «наиболее эффективно» с точки зрения времени, затрачиваемого программистом на его использование!
underscore_d
36

Несколько человек дали версии этого:

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

будьте осторожны с этим, потому что он будет усекать ваши фактические данные, если они длиннее, чем n.

Kevin
источник
17
@padstr = REPLICATE(@padchar, @len) -- this can be cached, done only once

SELECT RIGHT(@padstr + @str, @len)
Sklivvz
источник
9

Возможно, переубитие у меня есть эти UDFs, чтобы дополнить влево и вправо

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

и направо

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
источник
Единственная проблема со скалярными UDF состоит в том, что они работают намного хуже, чем эквивалентный встроенный код (плюс есть проблемы с типом данных). Мы надеемся, что они представят лучшую производительность скалярных UDF и / или встроенных скалярных UDF в будущей версии.
Cade Roux
Если вы укажете длину, меньшую длины переменной var, эти функции вернут null. Оберните каждое из повторяющихся утверждений оператором isnull, чтобы просто возвращать var, если длина меньше. isnull (копия (...), '')
Джерси Чувак
Добавление WITH SCHEMABINDING к объявлению функции повысит эффективность пользовательских функций. Это то, что я рекомендую добавить по умолчанию ко всем вашим функциям, если только у вас нет веских причин для их удаления.
EricI
7

Я не уверен, что метод, который вы даете, действительно неэффективен, но альтернативный способ, если он не должен быть гибким по длине или символу заполнения, будет (при условии, что вы хотите дополнить его " От 0 "до 10 символов:

DECLARE
   @pad_characters VARCHAR(10)

SET @pad_characters = '0000000000'

SELECT RIGHT(@pad_characters + @str, 10)
Том Х
источник
2

вероятно, перебор, я часто использую этот 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

Так что вы можете делать такие вещи, как:

select dbo.f_pad_before('aaa', 10, '_')
АМП
источник
Это на самом деле используется в udf, который должен сделать несколько других вещей, чтобы соответствовать некоторым данным.
Cade Roux
Это также отлично работает, если вы комбинируете типы char и varchar.
Джимми Бейкер
2

Мне понравилось решение vnRocks, вот оно в виде 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
источник
2

это простой способ дополнить левую панель:

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

Где xномер пэда и yего символ.

образец:

REPLACE(STR(FACT_HEAD.FACT_NO, 3, 0), ' ', 0)
Ahmad
источник
Вы, кажется, говорите о таких числах слева, что 1становится 001.
Мартин Смит
1
select right(replicate(@padchar, @len) + @str, @len)
Гордон Белл
источник
1

В SQL Server 2005 и более поздних версиях для этого можно создать функцию CLR.

Kramii
источник
1

Как насчет этого:

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

3 - число zerosколодок

joshblair
источник
Это помогло мне: CONCAT (ЗАМЕНА (ПРОСТРАНСТВО (@n - LENGTH (@str)), '', '0'), @str)
ingham
1

Я надеюсь, что это поможет кому-то.

STUFF ( character_expression , start , length ,character_expression )

select stuff(@str, 1, 0, replicate('0', @n - len(@str)))
vnRock
источник
0

Я использую это. Это позволяет вам определить длину, которую вы хотите, чтобы результат был, а также символ заполнения по умолчанию, если он не указан. Конечно, вы можете настроить длину ввода и вывода для любых максимумов, с которыми вы сталкиваетесь.

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

Ваш пробег может варьироваться. :-)

Джои Морган,
главный программист / аналитик I-
подразделения WellPoint Medicaid

Джозеф Морган
источник
0

Вот мое решение, которое избегает усеченных строк и использует простой старый SQL. Благодаря @AlexCuse , @Kevin и @Sklivvz , решения которых являются основой этого кода.

 --[@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;
Mass Dot Net
источник
0

Я знаю, что это немного добавляет к разговору в данный момент, но я запускаю процедуру генерации файлов, и она идет невероятно медленно. Я использовал дубликат и увидел этот метод обрезки и решил, что я бы попробовал.

Вы можете видеть в моем коде, где переключение между ними является дополнением к новой переменной @padding (и ограничению, которое теперь существует). Я выполнил свою процедуру с функцией в обоих состояниях с одинаковыми результатами во время выполнения. Так что по крайней мере в SQLServer2016 я не вижу никакой разницы в эффективности, которую нашли другие.

В любом случае, вот мой UDF, который я написал несколько лет назад, плюс сегодняшние изменения, которые во многом совпадают с другими, за исключением того, что у него есть опция параметра LEFT / RIGHT и некоторая проверка ошибок.

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
источник
0

У меня есть одна функция, которая lpad с x десятичными числами: CREATE FUNCTION [dbo]. [LPAD_DEC] (- Добавьте здесь параметры для функции @pad nvarchar (MAX), @string nvarchar (MAX), @length int, @dec int ) ВОЗВРАЩАЕТСЯ nvarchar (max) КАК НАЧАТЬ - Объявите здесь возвращаемую переменную 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

КОНЕЦ

Панчо Р
источник
-1

Чтобы обеспечить числовые значения, округленные до двух десятичных разрядов, но при необходимости добавленные справа с нулями, я имею:

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)

Если кто -то может думать о аккуратным образом, что будет оценено - выше кажется неуклюжим .

Примечание : в этом случае я использую SQL Server для отправки отчетов по электронной почте в формате HTML и поэтому хочу отформатировать информацию без привлечения дополнительного инструмента для анализа данных.

mattpm
источник
1
Не знал, что SQL Server позволяет вам объявлять переменную без указания ее типа. В любом случае, ваш метод кажется «неуклюжим» для нерабочего. :)
Андрей М
-4

Вот как я бы обычно дополнял varchar

WHILE Len(@String) < 8
BEGIN
    SELECT @String = '0' + @String
END
Deanos
источник
14
Вау, это удивительно плохо.
Хоган
1
Циклы, курсоры и т. Д. Вообще плохо в SQL. Может быть хорошо в коде приложения, но не в SQL. Некоторые исключения, но это не одно из них.
Давос,