Cara T-SQL paling efisien untuk mengisi varchar di sebelah kiri hingga panjang tertentu?


205

Dibandingkan dengan mengatakan:

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

Saya memutar kembali edit terakhir. Pertanyaannya memberi satu cara - saya mencari cara yang lebih optimal. Hasil edit kehilangan implikasi dalam mencari beberapa kualitas lain.
Cade Roux

Jawaban:


323

Ini hanyalah penggunaan SQL yang tidak efisien, tidak peduli bagaimana Anda melakukannya.

mungkin sesuatu seperti

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

di mana X adalah karakter padding Anda dan @n adalah jumlah karakter dalam string yang dihasilkan (dengan asumsi Anda membutuhkan padding karena Anda berurusan dengan panjang tetap).

Tetapi seperti yang saya katakan Anda harus benar-benar menghindari melakukan ini di database Anda.


2
Ada kalanya diperlukan ... misalnya, mengambil subset data untuk layar halaman.
Bip bip

7
+1 Baru saja menguji banyak metode yang berbeda dan ini yang tercepat. Anda mungkin perlu berpikir RTRIM(@str)apakah itu bisa mengandung spasi tambahan.
Martin Smith

1
+1 bingung mengapa char saya (6) tidak padding dengan benar, dan RTRIM pada variabel input menyelamatkan saya beberapa goresan kepala
jkelley

@ MartinSmith Terima kasih ... Saya sudah mencoba mencari tahu mengapa string saya tidak berfungsi dan rtrim memperbaikinya. TY.
WernerCD

3
Ini jawaban yang bagus. Tentu saja Anda harus menghindari melakukan ini ketika Anda tidak harus melakukannya, tetapi kadang-kadang itu tidak dapat dihindari; dalam kasus saya, saya tidak punya pilihan untuk melakukannya di C # karena kendala penyebaran, dan seseorang menyimpan nomor waralaba sebagai INT ketika seharusnya string numerik 5 karakter dengan nol di depan. Ini sangat membantu.
Jim

57

Saya tahu ini awalnya diminta kembali pada tahun 2008, tetapi ada beberapa fungsi baru yang diperkenalkan dengan SQL Server 2012. Fungsi FORMAT menyederhanakan bantalan kiri dengan nol dengan baik. Itu juga akan melakukan konversi untuk Anda:

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

Memperbarui:

Saya ingin menguji sendiri efisiensi fungsi FORMAT. Saya cukup terkejut menemukan efisiensi tidak terlalu baik dibandingkan dengan jawaban asli dari AlexCuse . Meskipun saya menemukan fungsi FORMAT lebih bersih, tidak terlalu efisien dalam hal waktu eksekusi. Tabel Tally yang saya gunakan memiliki 64.000 catatan. Kudos to Martin Smith untuk menunjukkan efisiensi waktu eksekusi.

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

Waktu Eksekusi SQL Server: Waktu CPU = 2157 ms, waktu yang berlalu = 2696 ms.

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

Waktu Eksekusi SQL Server:

Waktu CPU = 31 ms, waktu yang berlalu = 235 ms.


1
Ini persis apa yang saya cari. Bantuan resmi untuk FORMAT: msdn.microsoft.com/es-MX/library/hh213505.aspx
Fer García

1
Ini berlaku untuk SQL Server 2014 dan seterusnya sesuai dengan MSDN dan pengalaman saya sendiri untuk mencobanya di SQL Server 2012.
arame3333

5
Ini tidak akan menjadi cara "paling efisien" seperti yang diminta sekalipun. Dalam contoh ini, Format membutuhkan waktu 180 detik vs 12 detik. stackoverflow.com/a/27447244/73226
Martin Smith

2
Ini mungkin "paling efisien" dalam hal waktu yang dibutuhkan oleh programmer untuk memanfaatkannya!
underscore_d

36

Beberapa orang memberikan versi ini:

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

hati-hati dengan itu karena akan memotong data aktual Anda jika lebih panjang dari n.


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

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

9

Mungkin over-kill, saya punya UDF ini di sebelah kiri dan kanan

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

dan ke kanan

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

Satu-satunya masalah dengan skalar UDF adalah bahwa kinerjanya jauh lebih buruk daripada inline kode yang setara (ditambah ada masalah tipe data). Untuk berharap mereka memperkenalkan kinerja skalar UDF yang lebih baik dan / atau skalar UDF inline dalam versi masa depan.
Cade Roux

Jika Anda menentukan panjang kurang dari panjang var, maka fungsi-fungsi ini mengembalikan nol. Bungkus masing-masing pernyataan replikasi dengan pernyataan isnull untuk mengembalikan var jika panjangnya kurang. isnull (replicate (...), '')
Jersey Dude

Menambahkan DENGAN MEMASUKKAN ke deklarasi fungsi akan meningkatkan efisiensi fungsi yang ditentukan pengguna. Ini adalah sesuatu yang saya sarankan tambahkan secara default ke semua fungsi Anda kecuali Anda memiliki alasan kuat untuk menghapusnya.
EricI

7

Saya tidak yakin metode yang Anda berikan benar-benar tidak efisien, tetapi cara alternatif, asalkan tidak harus fleksibel dalam panjang atau karakter padding, akan menjadi (dengan asumsi bahwa Anda ingin menambahkannya dengan " 0 "hingga 10 karakter:

DECLARE
   @pad_characters VARCHAR(10)

SET @pad_characters = '0000000000'

SELECT RIGHT(@pad_characters + @str, 10)

2

mungkin berlebihan, saya sering menggunakan UDF ini:

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

Sehingga Anda dapat melakukan hal-hal seperti:

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

Ini sebenarnya digunakan dalam udf yang harus melakukan beberapa hal lain untuk menyesuaikan beberapa data.
Cade Roux

Ini juga berfungsi dengan baik jika Anda menggabungkan tipe char dan varchar.
Jimmy Baker

2

Saya suka solusi vnRocks, ini dia dalam bentuk 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

ini adalah cara sederhana untuk menulis kiri:

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

Di mana xnomor pad dan ykarakter pad.

Sampel:

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

Anda tampaknya berbicara tentang nomor padding kiri sehingga 1menjadi 001.
Martin Smith


1

Di SQL Server 2005 dan yang lebih baru Anda bisa membuat fungsi CLR untuk melakukan ini.


1

Bagaimana dengan ini:

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

3 adalah jumlah zerospad


Ini membantu saya: CONCAT (REPLACE (SPACE (@n - LENGTH (@str)), '', '0'), @str)
ingham

1

Saya harap ini membantu seseorang.

STUFF ( character_expression , start , length ,character_expression )

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

0

Saya menggunakan yang ini. Ini memungkinkan Anda untuk menentukan panjang yang Anda inginkan hasilnya serta karakter padding default jika tidak disediakan. Tentu saja Anda dapat menyesuaikan panjang input dan output untuk berapa pun maksimum yang Anda temui.

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

Jarak tempuh Anda mungkin beragam. :-)

Joey Morgan
Programmer / Analis Kepala I
Unit Bisnis Medicaid I WellPoint


0

Inilah solusi saya, yang menghindari string terpotong dan menggunakan SQL sederhana. Berkat @AlexCuse , @ Kevin dan @Sklivvz , yang solusi adalah dasar dari kode ini.

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

Saya tahu ini tidak menambah banyak percakapan pada saat ini, tetapi saya sedang menjalankan prosedur pembuatan file dan itu akan sangat lambat. Saya telah menggunakan replikat dan melihat metode trim ini dan berpikir saya akan mencobanya.

Anda dapat melihat dalam kode saya di mana pergantian di antara keduanya merupakan tambahan pada variabel @padding baru (dan batasan yang sekarang ada). Saya menjalankan prosedur saya dengan fungsi di kedua negara dengan hasil yang sama dalam waktu eksekusi. Jadi setidaknya di SQLServer2016, saya tidak melihat perbedaan dalam efisiensi yang ditemukan orang lain.

Ngomong-ngomong, ini UDF saya yang saya tulis bertahun-tahun yang lalu ditambah perubahan hari ini yang sama seperti yang lain selain memiliki opsi param KIRI / KANAN dan beberapa pengecekan kesalahan.

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

Saya punya satu fungsi yang lpad dengan x desimal: CREATE FUNCTION [dbo]. [LPAD_DEC] (- Tambahkan parameter untuk fungsi di sini @pad nvarchar (MAX), @string nvarchar (MAX), @length int, @dec int ) RETURNS nvarchar (maks) AS BEGIN - Deklarasikan variabel return di sini DECLARE @resp nvarchar (maks)

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

AKHIR


-1

Untuk memberikan nilai numerik dibulatkan ke dua tempat desimal tetapi diisi dengan nol jika diperlukan, saya harus:

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)

Jika ada yang bisa memikirkan cara yang lebih rapi, itu akan dihargai - hal di atas nampak canggung .

Catatan : dalam contoh ini, saya menggunakan SQL Server untuk mengirim email laporan dalam format HTML dan ingin memformat informasi tanpa melibatkan alat tambahan untuk mengurai data.


1
Tidak tahu SQL Server memungkinkan Anda untuk mendeklarasikan variabel tanpa menentukan jenisnya. Bagaimanapun, metode Anda tampaknya "canggung" untuk yang tidak berfungsi. :)
Andriy M

-4

Ini adalah bagaimana saya biasanya membuat varchar

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

14
Wow, ini sangat buruk.
Hogan

1
Loop, kursor, dll semuanya umumnya buruk di SQL. Mungkin baik-baik saja dalam kode aplikasi tetapi tidak dalam SQL. Beberapa pengecualian tapi ini bukan salah satunya.
Davos
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.