วิธี T-SQL ที่มีประสิทธิภาพมากที่สุดในการรอง varchar ทางด้านซ้ายถึงความยาวที่แน่นอน?


205

เมื่อเทียบกับพูดว่า:

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

ฉันย้อนกลับการแก้ไขครั้งล่าสุด คำถามให้วิธีเดียว - ฉันกำลังมองหาวิธีที่เหมาะสมที่สุด การแก้ไขหายไปซึ่งเกี่ยวข้องกับการค้นหาคุณภาพอื่น ๆ
Cade Roux

คำตอบ:


323

นี่เป็นเพียงการใช้ SQL ที่ไม่มีประสิทธิภาพไม่ว่าคุณจะทำอย่างไร

บางทีสิ่งที่ชอบ

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

โดยที่ X คืออักขระการขยายของคุณและ @n คือจำนวนอักขระในสตริงผลลัพธ์ (สมมติว่าคุณต้องการการขยายเนื่องจากคุณกำลังติดต่อกับความยาวคงที่)

แต่อย่างที่ฉันบอกคุณควรหลีกเลี่ยงการทำเช่นนี้ในฐานข้อมูลของคุณ


2
มีบางครั้งที่มันจำเป็น ... ตัวอย่างเช่นการดึงชุดย่อยของข้อมูลสำหรับหน้าจอเพจ
ปี๊บปี๊บ

7
+1 เพิ่งทดสอบวิธีการต่าง ๆ มากมายและนี่เป็นวิธีที่เร็วที่สุด คุณอาจจำเป็นต้องมีRTRIM(@str)แม้ว่าจะมีช่องว่างต่อท้าย
Martin Smith

1
+1 งงงวยว่าทำไมถ่านของฉัน (6) ไม่ถูกรองอย่างถูกต้องและ RTRIM ในตัวแปรอินพุตช่วยให้ฉันเกาหัวได้บ้าง
jkelley

@MartinSmith ขอบคุณ ... ฉันพยายามคิดดูแล้วว่าทำไมสตริงของฉันถึงใช้งานไม่ได้และ rtrim ก็ซ่อมมัน TY
WernerCD

3
นี่คือคำตอบที่ดี แน่นอนคุณควรหลีกเลี่ยงการทำเช่นนี้เมื่อคุณไม่จำเป็นต้องทำ แต่บางครั้งมันก็หลีกเลี่ยงไม่ได้ ในกรณีของฉันฉันไม่มีทางเลือกที่จะทำใน C # เนื่องจากข้อ จำกัด ในการปรับใช้และมีคนเก็บหมายเลขแฟรนไชส์เป็น INT เมื่อมันควรจะเป็นสตริงตัวเลข 5 ตัวที่มีศูนย์นำหน้า สิ่งนี้ช่วยได้อย่างมาก
จิม

57

ฉันรู้ว่าสิ่งนี้ได้รับการถามย้อนกลับไปในปี 2008 แต่มีฟังก์ชั่นใหม่บางอย่างที่ถูกนำมาใช้กับ SQL Server 2012 ฟังก์ชัน FORMATช่วยให้การเติมเต็มง่ายขึ้นด้วยศูนย์ทางซ้าย มันจะทำการแปลงให้คุณด้วย:

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

ปรับปรุง:

ฉันต้องการทดสอบประสิทธิภาพที่แท้จริงของฟังก์ชัน FORMAT ด้วยตนเอง ฉันรู้สึกประหลาดใจมากทีเดียวที่จะหาประสิทธิภาพก็ไม่ได้ดีมากเมื่อเทียบกับคำตอบจากเดิมAlexCuse แม้ว่าฉันจะพบว่าฟังก์ชั่นฟอร์แมทสะอาดขึ้น แต่ก็ไม่ได้มีประสิทธิภาพมากนักในแง่ของเวลาในการทำงาน ตาราง Tally ที่ฉันใช้มี 64,000 รายการ รุ่งโรจน์ถึงMartin Smithเพื่อชี้ประสิทธิภาพเวลาในการปฏิบัติงาน

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

เวลาดำเนินการของ SQL Server: เวลา CPU = 2157 ms, เวลาที่ผ่านไป = 2696 ms

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

เวลาดำเนินการของ SQL Server:

เวลา CPU = 31 ms, เวลาที่ผ่านไป = 235 ms


1
นี่คือสิ่งที่ฉันกำลังมองหา ความช่วยเหลืออย่างเป็นทางการสำหรับ FORMAT: msdn.microsoft.com/es-MX/library/hh213505.aspx
Fer García

1
สิ่งนี้ใช้กับ SQL Server 2014 เป็นต้นไปตาม MSDN และประสบการณ์ของฉันในการลองบน SQL Server 2012
arame3333

5
นี่จะไม่ใช่วิธีที่ "มีประสิทธิภาพมากที่สุด" ตามที่ถามไว้ ในตัวอย่างนี้รูปแบบใช้เวลา 180 วินาทีกับ 12 วินาที stackoverflow.com/a/27447244/73226
Martin Smith

2
มันอาจจะ "มีประสิทธิภาพมากที่สุด" ในแง่ของเวลาที่โปรแกรมเมอร์ใช้ประโยชน์จากมัน!
underscore_d

36

หลายคนให้รุ่นนี้:

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

ระวังด้วยเพราะมันจะตัดทอนข้อมูลจริงของคุณถ้ามันยาวกว่า n



9

บางทีการฆ่ามากกว่าฉันมี UDF เหล่านี้เพื่อรองซ้ายและขวา

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

ปัญหาเดียวกับสเกลาร์ UDF คือสเกลาร์ทำงานได้ไม่ดีกว่าอินไลน์โค้ดที่เทียบเท่า (รวมถึงปัญหาประเภทข้อมูล) นี่คือความหวังว่าพวกเขาจะนำเสนอประสิทธิภาพ UDF สเกลาร์ที่ดีขึ้นและ / หรือสเกลาร์แบบอินไลน์ UDF ในเวอร์ชันอนาคต
เคด Roux

หากคุณระบุความยาวน้อยกว่าความยาวของ var ฟังก์ชันเหล่านี้จะคืนค่า null ล้อมรอบคำสั่งที่ซ้ำกันด้วยคำสั่ง isnull เพื่อคืนค่า var หากความยาวน้อยกว่า isnull (ทำซ้ำ (... ), '')
Jersey Dude

การเพิ่ม SCHEMABINDING กับการประกาศฟังก์ชั่นจะช่วยเพิ่มประสิทธิภาพของฟังก์ชั่นที่ผู้ใช้กำหนด นี่คือสิ่งที่ฉันแนะนำให้เพิ่มโดยค่าเริ่มต้นในทุกฟังก์ชั่นของคุณยกเว้นว่าคุณมีเหตุผลที่น่าสนใจในการลบออก
EricI

7

ฉันไม่แน่ใจว่าวิธีการที่คุณให้นั้นไม่มีประสิทธิภาพจริงๆ แต่เป็นทางเลือกตราบใดที่มันไม่จำเป็นต้องมีความยืดหยุ่นในความยาวหรือความยาวของตัวละครจะเป็น (สมมติว่าคุณต้องการรองด้วย " 0 "ถึง 10 ตัวอักษร:

DECLARE
   @pad_characters VARCHAR(10)

SET @pad_characters = '0000000000'

SELECT RIGHT(@pad_characters + @str, 10)

2

อาจ overkill ฉันมักจะใช้ 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 ซึ่งต้องทำบางสิ่งเพื่อให้สอดคล้องกับข้อมูลบางอย่าง
เคด 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

2

นี่เป็นวิธีง่ายๆในการรองซ้าย:

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

อยู่ที่ไหนxหมายเลขแผ่นและyเป็นตัวละครแผ่น

ตัวอย่าง:

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

ดูเหมือนว่าคุณจะพูดคุยเกี่ยวกับตัวเลขการขยายซ้ายเช่นนั้นจะกลายเป็น1 001
Martin Smith


1

ใน SQL Server 2005 และใหม่กว่าคุณสามารถสร้างฟังก์ชั่น CLR เพื่อทำสิ่งนี้


1

เกี่ยวกับสิ่งนี้:

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

3 คือจำนวนของzerosแผ่น


สิ่งนี้ช่วยให้ฉัน: CONCAT (แทนที่ (SPACE (@n - LENGTH (@str)), '', '0'), @str)
ingham

1

ฉันหวังว่านี่จะช่วยให้ใครบางคน

STUFF ( character_expression , start , length ,character_expression )

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

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

ไมล์สะสมของคุณอาจแตกต่างกันไป :-)

Joey Morgan
โปรแกรมเมอร์ / นักวิเคราะห์หลัก I
หน่วยธุรกิจ Medicaid WellPoint


0

นี่คือทางออกของฉันซึ่งหลีกเลี่ยงสตริงที่ถูกตัดทอนและใช้ล้วน ol '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;

0

ฉันรู้ว่านี่ไม่ได้เพิ่มการสนทนามากในตอนนี้ แต่ฉันกำลังเรียกใช้ขั้นตอนการสร้างไฟล์และมันจะช้าอย่างไม่น่าเชื่อ ฉันใช้การทำซ้ำและเห็นวิธีการเล็มนี้และคิดว่าฉันจะให้มันยิง

คุณสามารถดูได้ในรหัสของฉันที่สวิตช์ทั้งสองนั้นอยู่นอกเหนือจากตัวแปร @padding ใหม่ (และข้อ จำกัด ที่มีอยู่ในขณะนี้) ฉันรันโพรซีเดอร์ของฉันด้วยฟังก์ชันในทั้งสองสถานะด้วยผลลัพธ์เดียวกันในเวลาดำเนินการ อย่างน้อยใน SQLServer2016 ฉันไม่เห็นความแตกต่างของประสิทธิภาพที่คนอื่น ๆ พบ

ต่อไปนี่คือ UDF ของฉันที่ฉันเขียนเมื่อหลายปีก่อนรวมถึงการเปลี่ยนแปลงในวันนี้ซึ่งเหมือนกับตัวอื่น ๆ นอกเหนือจากที่มีตัวเลือกพารามิเตอร์ซ้าย / ขวาและการตรวจสอบข้อผิดพลาดบางอย่าง

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

ฉันมีฟังก์ชั่นหนึ่งที่ lpad ด้วย x ทศนิยม: สร้างฟังก์ชั่น [dbo] [LPAD_DEC] (- เพิ่มพารามิเตอร์สำหรับฟังก์ชั่นที่นี่ @pad nvarchar (MAX), @string nvarchar (MAX), @length int, @dec int ) ผลตอบแทน nvarchar (สูงสุด) เป็นเริ่มต้น - ประกาศตัวแปรกลับที่นี่ DECLARE @resp nvarchar (สูงสุด)

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

END


-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 และต้องการจัดรูปแบบข้อมูลโดยไม่ต้องใช้เครื่องมือเพิ่มเติมในการแยกวิเคราะห์ข้อมูล


1
ไม่ทราบว่า SQL Server อนุญาตให้คุณประกาศตัวแปรโดยไม่ระบุประเภทของมัน อย่างไรก็ตามวิธีการของคุณดูเหมือน "เงอะงะ" สำหรับคนที่ไม่ทำงาน :)
Andriy M

-4

นี่คือวิธีปกติฉันจะรอง varchar

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

14
ว้าวนี่มันแย่มาก
โฮแกน

1
ลูปเม้าส์และอื่น ๆ โดยทั่วไปจะไม่ดีใน SQL อาจใช้ได้ในโค้ดแอปพลิเคชัน แต่ไม่ใช่ใน SQL ข้อยกเว้นบางอย่าง แต่นี่ไม่ใช่หนึ่งในนั้น
Davos
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.