สั่งซื้อโดยการเปรียบเทียบตัวอักษรและตัวเลขผสมกัน


9

เราจำเป็นต้องทำการรายงานบางอย่างเกี่ยวกับค่าที่มักจะรวมสตริงของตัวเลขและตัวอักษรที่ต้องเรียงลำดับ 'ตามธรรมชาติ' สิ่งที่ต้องการเช่น "P7B18" หรือ "P12B3" @ สายอักขระส่วนใหญ่จะเป็นลำดับของตัวอักษรจากนั้นจึงสลับตัวเลข จำนวนของกลุ่มเหล่านี้และความยาวของแต่ละกลุ่มอาจแตกต่างกันไป

เราต้องการเรียงลำดับตัวเลขเหล่านี้ตามลำดับตัวเลข เห็นได้ชัดว่าถ้าฉันจัดการค่าสตริงเหล่านั้นโดยตรงด้วยORDER BY"P12B3" จะมาก่อน "P7B18" เนื่องจาก "P1" เก่ากว่า "P7" แต่ฉันต้องการย้อนกลับเพราะ "P7" อยู่ก่อน "P12"

ฉันยังต้องการที่จะทำการเปรียบเทียบช่วงเช่น@bin < 'P13S6'หรือบางอย่างเช่น ฉันไม่ต้องจัดการกับจำนวนจุดลอยตัวหรือจำนวนลบ; สิ่งเหล่านี้จะเป็นจำนวนเต็มที่ไม่ติดลบที่เรากำลังทำอยู่ ความยาวสตริงและจำนวนของเซกเมนต์อาจเป็นไปได้เองโดยไม่ จำกัด ขอบเขต

ในกรณีของเราปลอกสตริงนั้นไม่สำคัญแม้ว่าจะมีวิธีการในการเปรียบเทียบการเรียงตัว แต่คนอื่น ๆ อาจพบว่ามีประโยชน์ ส่วนที่น่าเกลียดที่สุดของทั้งหมดนี้คือฉันต้องการที่จะทำทั้งการสั่งซื้อและการกรองช่วงในWHEREข้อ

ถ้าฉันทำสิ่งนี้ใน C # มันจะเป็นงานที่ค่อนข้างง่าย: ทำการแยกวิเคราะห์เพื่อแยกอัลฟาจากตัวเลขใช้ IComparable และคุณก็ทำได้โดยทั่วไป แน่นอนว่า SQL Server ไม่ได้เสนอฟังก์ชั่นที่คล้ายกันอย่างน้อยที่สุดเท่าที่ฉันทราบ

ใครรู้เทคนิคที่ดีในการทำงานนี้หรือไม่? มีความสามารถเล็กน้อยเผยแพร่ในการสร้างประเภท CLR ที่กำหนดเองที่ใช้ IComparable และมีพฤติกรรมนี้ตามที่คาดไว้? ฉันยังไม่ได้คัดค้าน Stupid XML Tricks (ดูเพิ่มเติมที่: การต่อข้อมูลรายการ) และฉันมี CLR regex การจับคู่ / การแยก / การแยก / การแทนที่ฟังก์ชั่น wrapper ที่มีอยู่บนเซิร์ฟเวอร์เช่นกัน

แก้ไข: ตามตัวอย่างที่ละเอียดกว่าเล็กน้อยฉันต้องการให้ข้อมูลทำอะไรแบบนี้

SELECT bin FROM bins ORDER BY bin

bin
--------------------
M7R16L
P8RF6JJ
P16B5
PR7S19
PR7S19L
S2F3
S12F0

ie แบ่งสตริงเป็นโทเค็นของตัวอักษรทั้งหมดหรือตัวเลขทั้งหมดและเรียงลำดับตามลำดับตัวอักษรหรือตัวเลขตามลำดับโดยโทเค็นซ้ายสุดเป็นคำที่เรียงลำดับที่สำคัญที่สุด อย่างที่ฉันบอกไปแล้วว่าชิ้นส่วนของเค้กใน. NET ถ้าคุณใช้ IComparable แต่ฉันไม่รู้ว่าคุณสามารถทำสิ่งนั้นใน SQL Server ได้อย่างไร (หรือถ้า) แน่นอนว่าไม่ใช่สิ่งที่ฉันเคยเจอมาใน 10 ปีหรือมากกว่านั้นด้วยการทำงานกับมัน


คุณสามารถทำสิ่งนี้กับคอลัมน์ที่คำนวณได้บางประเภททำให้สตริงกลายเป็นจำนวนเต็ม ดังนั้นP7B12อาจกลายเป็นP 07 B 12แล้ว (ผ่าน ASCII) 80 07 65 12ดังนั้น80076512
Philᵀᴹ

ฉันขอแนะนำให้คุณสร้างคอลัมน์จากการคำนวณซึ่งจะรวมส่วนประกอบตัวเลขแต่ละตัวเข้ากับความยาวขนาดใหญ่ (เช่น 10 ศูนย์) เนื่องจากรูปแบบค่อนข้างเหมาะสมคุณจะต้องมีการแสดงออกแบบอินไลน์ขนาดใหญ่ แต่ทำได้ จากนั้นคุณสามารถจัดทำดัชนี / สั่งซื้อตาม / ที่ไหนในคอลัมน์นั้นได้มากเท่าที่คุณต้องการ
Nick.McD เงือก

โปรดดูที่ลิงค์ฉันเพิ่งเพิ่มไปด้านบนของคำตอบของฉัน :)
โซโลมอน Rutzky

1
@ srutzky ดีฉันลงคะแนนให้
db2

สวัสดี db2: เนื่องจาก Microsoft ย้ายจากเชื่อมต่อกับ UserVoice และไม่นับจำนวนการโหวตอย่างแน่นอน (พวกเขาใส่ไว้ในความคิดเห็น แต่ไม่แน่ใจว่าพวกเขามองที่นั้น) คุณอาจต้องลงคะแนนใหม่: สนับสนุน "การจัดเรียงตามธรรมชาติ" / DIGITSASNUMBERS เป็นตัวเลือกเปรียบเทียบ ขอบคุณ!
โซโลมอน Rutzky

คำตอบ:


8

ต้องการวิธีการเรียงลำดับตัวเลขที่สมเหตุสมผลและมีประสิทธิภาพเป็นตัวเลขจริงหรือไม่? พิจารณาการลงคะแนนสำหรับคำแนะนำ Microsoft Connect ของฉัน: สนับสนุน "การเรียงลำดับโดยธรรมชาติ" / DIGITSASNUMBERS เป็นตัวเลือกการจัดเรียง


ไม่มีวิธีง่ายๆในตัวในการทำสิ่งนี้ แต่นี่เป็นความเป็นไปได้:

ทำให้ปกติสตริงโดยจัดรูปแบบพวกเขาเป็นเซ็กเมนต์ความยาวคงที่:

  • VARCHAR(50) COLLATE Latin1_General_100_BIN2สร้างคอลัมน์เรียงลำดับของประเภท ความยาวสูงสุด 50 อาจต้องปรับตามจำนวนกลุ่มสูงสุดและความยาวสูงสุดที่เป็นไปได้
  • ในขณะที่การทำให้เป็นมาตรฐานสามารถทำได้ในเลเยอร์แอปได้อย่างมีประสิทธิภาพมากขึ้นการจัดการสิ่งนี้ในฐานข้อมูลโดยใช้ T-SQL UDF จะอนุญาตให้วาง UDF สเกลาร์ไว้ในAFTER [or FOR] INSERT, UPDATEทริกเกอร์ การเข้ามาผ่านการสอบถามเฉพาะกิจ ฯลฯ แน่นอนว่า UDF สเกลาร์นั้นสามารถจัดการผ่าน SQLCLR ได้ด้วย แต่จะต้องมีการทดสอบเพื่อตรวจสอบว่าอันไหนมีประสิทธิภาพมากขึ้น **
  • UDF (ไม่ว่าจะอยู่ใน T-SQL หรือ SQLCLR) ควร:
    • ประมวลผลจำนวนเซ็กเมนต์ที่ไม่รู้จักโดยการอ่านอักขระแต่ละตัวและหยุดเมื่อชนิดเปลี่ยนจากอัลฟ่าเป็นตัวเลขหรือตัวเลขเป็นอัลฟา
    • ในแต่ละเซกเมนต์ควรส่งคืนสตริงที่มีความยาวคงที่ซึ่งตั้งค่าเป็นอักขระ / ตัวเลขสูงสุดที่เป็นไปได้ของส่วนใด ๆ (หรืออาจจะเพิ่ม + 1 หรือ 2 เพื่อบัญชีสำหรับการเติบโตในอนาคต)
    • เซ็กเมนต์อัลฟ่าควรจัดชิดซ้ายและมีช่องว่างด้านขวา
    • ส่วนที่เป็นตัวเลขควรเป็นธรรมและชิดซ้ายด้วยศูนย์
    • หากตัวอักษรอัลฟ่าสามารถเข้ามาเป็นตัวพิมพ์ใหญ่ - เล็กได้ แต่การเรียงลำดับต้องไม่คำนึงถึงตัวพิมพ์ใหญ่ - เล็กให้ใช้UPPER()ฟังก์ชันกับผลลัพธ์สุดท้ายของทุกส่วน สิ่งนี้จะช่วยให้การเรียงลำดับที่เหมาะสมได้รับการเปรียบเทียบไบนารีของคอลัมน์เรียงลำดับ
  • สร้างAFTER INSERT, UPDATEทริกเกอร์บนตารางที่เรียกใช้ UDF เพื่อตั้งค่าคอลัมน์เรียงลำดับ เพื่อปรับปรุงประสิทธิภาพการUPDATE()ทำงานให้ใช้ฟังก์ชั่นเพื่อตรวจสอบว่าคอลัมน์รหัสนี้อยู่ในSETประโยคของUPDATEคำสั่ง (เพียงแค่RETURNเป็นเท็จ) จากนั้นเข้าร่วมINSERTEDและDELETEDตารางหลอกในคอลัมน์รหัสเพื่อประมวลผลแถวที่มีการเปลี่ยนแปลงในค่ารหัสเท่านั้น . ตรวจสอบให้แน่ใจว่าได้ระบุCOLLATE Latin1_General_100_BIN2เงื่อนไข JOIN นั้นเพื่อความถูกต้องในการพิจารณาว่ามีการเปลี่ยนแปลงหรือไม่
  • สร้างดัชนีในคอลัมน์เรียงลำดับใหม่

ตัวอย่าง:

P7B18   -> "P     000007B     000018"
P12B3   -> "P     000012B     000003"
P12B3C8 -> "P     000012B     000003C     000008"

ในวิธีการนี้คุณสามารถจัดเรียงผ่าน:

ORDER BY tbl.SortColumn

และคุณสามารถทำการกรองพิสัยผ่าน:

WHERE tbl.SortColumn BETWEEN dbo.MyUDF('P7B18') AND dbo.MyUDF('P12B3')

หรือ:

DECLARE @RangeStart VARCHAR(50),
        @RangeEnd VARCHAR(50);
SELECT @RangeStart = dbo.MyUDF('P7B18'),
       @RangeEnd = dbo.MyUDF('P12B3');

WHERE tbl.SortColumn BETWEEN @RangeStart AND @RangeEnd

ทั้งตัวกรองORDER BYและWHEREตัวกรองควรใช้การจัดเรียงแบบไบนารีที่กำหนดไว้SortColumnเนื่องจากการลำดับความสำคัญของการเรียงลำดับ

การเปรียบเทียบความเท่าเทียมกันจะยังคงทำในคอลัมน์ค่าดั้งเดิม


ความคิดอื่น ๆ:

  • ใช้ SQLCLR UDT สิ่งนี้อาจใช้งานได้แม้ว่ามันจะไม่ชัดเจนถ้ามันแสดงผลกำไรสุทธิเมื่อเทียบกับวิธีการที่อธิบายไว้ข้างต้น

    ใช่ SQLCLR UDT สามารถแทนที่โอเปอเรเตอร์การเปรียบเทียบได้ด้วยอัลกอริธึมที่กำหนดเอง สิ่งนี้จะจัดการสถานการณ์ที่ค่าถูกเปรียบเทียบกับค่าอื่นที่เป็นประเภทกำหนดเองที่เหมือนกันอยู่แล้วหรือต้องถูกแปลงโดยปริยาย สิ่งนี้ควรจัดการตัวกรองช่วงในWHEREสภาพ

    สำหรับการเรียงลำดับ UDT เป็นชนิดคอลัมน์ปกติ (ไม่ใช่คอลัมน์ที่คำนวณ) นี่เป็นไปได้ก็ต่อเมื่อ UDT คือ "ไบต์สั่ง" การเป็น "ไบต์สั่ง" หมายถึงการแสดงไบนารีของ UDT (ซึ่งสามารถกำหนดได้ใน UDT) จะเรียงลำดับตามลำดับที่เหมาะสมตามธรรมชาติ สมมติว่าการเป็นตัวแทนไบนารีได้รับการจัดการคล้ายกับวิธีการที่อธิบายไว้ข้างต้นสำหรับคอลัมน์ VARCHAR (50) ที่มีเซ็กเมนต์ความยาวคงที่ที่มีเบาะซึ่งจะมีคุณสมบัติ หรือหากไม่ใช่เรื่องง่ายที่จะตรวจสอบให้แน่ใจว่าการสั่งการแทนค่าแบบไบนารีนั้นเป็นไปตามธรรมชาติในวิธีที่เหมาะสมคุณสามารถเปิดเผยวิธีการหรือคุณสมบัติของ UDT ที่ส่งออกค่าที่จะถูกสั่งอย่างถูกต้องและสร้างPERSISTEDคอลัมน์จากการคำนวณ วิธีการหรือทรัพย์สิน IsDeterministic = trueวิธีการจะต้องมีการกำหนดและทำเครื่องหมายว่าเป็น

    ประโยชน์ของวิธีนี้คือ:

    • ไม่จำเป็นต้องมีช่อง "ค่าดั้งเดิม"
    • ไม่จำเป็นต้องเรียก UDF เพื่อแทรกข้อมูลหรือเปรียบเทียบค่า สมมติว่าParseวิธีการ UDT ใช้เวลาในคุณค่าและแปลงมันแล้วคุณควรจะสามารถที่จะเพียงแค่ใส่ค่าตามธรรมชาติเป็นP7B18 P7B18และด้วยวิธีการแปลงที่ตั้งไว้ใน UDT เงื่อนไข WHERE จะอนุญาตให้ใช้เพียง P7B18`

    ผลที่ตามมาของวิธีนี้คือ:

    • เพียงแค่เลือกฟิลด์ที่จะส่งกลับการเป็นตัวแทนไบนารีถ้าใช้ byte สั่ง UDT เป็นประเภทข้อมูลคอลัมน์ หรือถ้าใช้PERSISTEDคอลัมน์ที่คำนวณได้บนคุณสมบัติหรือเมธอดของ UDT คุณจะได้รับการแทนค่าที่ส่งคืนโดยคุณสมบัติหรือเมธอด หากคุณต้องการP7B18ค่าดั้งเดิมคุณต้องเรียกใช้เมธอดหรือคุณสมบัติของ UDT ที่มีการให้รหัสเพื่อส่งคืนการแทนค่านั้น เนื่องจากคุณต้องแทนที่ToStringวิธีต่อไปนั่นเป็นตัวเลือกที่ดีสำหรับการให้สิ่งนี้
    • มันไม่ชัดเจน (อย่างน้อยสำหรับฉันในตอนนี้เนื่องจากฉันยังไม่ได้ทดสอบส่วนนี้) วิธีที่ง่าย / ยากที่จะทำการเปลี่ยนแปลงใด ๆ กับการเป็นตัวแทนไบนารี การเปลี่ยนการนำเสนอที่เก็บไว้การเรียงลำดับอาจต้องวางและเพิ่มฟิลด์อีกครั้ง นอกจากนี้การปล่อยแอสเซมบลีที่ประกอบด้วย UDT จะล้มเหลวหากใช้ในลักษณะใดวิธีหนึ่งดังนั้นคุณต้องแน่ใจว่าไม่มีสิ่งใดในสมัชชานอกเหนือจาก UDT นี้ คุณสามารถALTER ASSEMBLYแทนที่คำจำกัดความได้ แต่มีข้อ จำกัด บางประการ

      ในทางตรงกันข้ามVARCHAR()ฟิลด์เป็นข้อมูลที่ถูกตัดการเชื่อมต่อจากอัลกอริทึมดังนั้นมันจะต้องมีการปรับปรุงคอลัมน์เท่านั้น และหากมีหลายสิบล้านแถว (หรือมากกว่า) ก็สามารถทำได้ในรูปแบบแบทช์

  • ใช้ไลบรารีICUซึ่งอนุญาตให้ทำการเรียงลำดับตัวอักษรและตัวเลขได้จริง ในขณะที่ใช้งานได้ดีห้องสมุดมีสองภาษาเท่านั้น: C / C ++ และ Java ซึ่งหมายความว่าคุณอาจจำเป็นต้องปรับแต่งจะทำบางอย่างที่จะได้ไปทำงานใน Visual C ++ หรือมีโอกาสปิดที่รหัส Java สามารถแปลงเป็น MSIL ใช้IKVM มีโครงการด้าน. NET หนึ่งหรือสองโครงการที่เชื่อมโยงกับไซต์นั้นที่มีอินเทอร์เฟซ COM ที่สามารถเข้าถึงได้ในรหัสที่ได้รับการจัดการ แต่ฉันเชื่อว่าพวกเขายังไม่ได้รับการอัปเดตในขณะที่ฉันยังไม่ได้ลอง ทางออกที่ดีที่สุดที่นี่คือการจัดการสิ่งนี้ในเลเยอร์แอปโดยมีเป้าหมายในการสร้างคีย์การเรียงลำดับ จากนั้นคีย์การเรียงลำดับจะถูกบันทึกลงในคอลัมน์การเรียงลำดับใหม่

    นี่อาจไม่ใช่วิธีที่ใช้งานได้จริงที่สุด อย่างไรก็ตามมันก็ยังเจ๋งมากที่ความสามารถนั้นมีอยู่ ฉันให้รายละเอียดเพิ่มเติมตัวอย่างของคำตอบต่อไปนี้:

    มีการเรียงเรียงเพื่อจัดเรียงสตริงต่อไปนี้ตามลำดับต่อไปนี้ 1,2,3,6,10,10A, 10B, 11 หรือไม่

    แต่รูปแบบที่ใช้ในคำถามนั้นค่อนข้างง่ายกว่า สำหรับตัวอย่างที่แสดงว่ารูปแบบของรูปแบบที่ใช้ในคำถามนี้ใช้งานได้ด้วยโปรดไปที่หน้าต่อไปนี้:

    ICU Collation Demo

    ภายใต้ "การตั้งค่า" ให้ตั้งค่าตัวเลือก "ตัวเลข" เป็น "เปิด" และอื่น ๆ ควรตั้งค่าเป็น "เริ่มต้น" ถัดไปทางด้านขวาของปุ่ม "sort" ให้ยกเลิกการเลือกตัวเลือกสำหรับ "diff strengths" และตรวจสอบตัวเลือกสำหรับ "sort keys" จากนั้นแทนที่รายการในพื้นที่ข้อความ "ป้อนข้อมูล" ด้วยรายการต่อไปนี้:

    P12B22
    P7B18
    P12B3
    as456456hgjg6786867
    P7Bb19
    P7BA19
    P7BB19
    P007B18
    P7Bb20
    P7Bb19z23
    

    คลิกที่ปุ่ม "เรียงลำดับ" พื้นที่ข้อความ "ผลลัพธ์" ควรแสดงสิ่งต่อไปนี้:

    as456456hgjg6786867
        29 4D 0F 7A EA C8 37 35 3B 35 0F 84 17 A7 0F 93 90 , 0D , , 0D .
    P7B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P007B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P7BA19
        47 0F 09 2B 29 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19
        47 0F 09 2B 2B 0F 15 , 09 , FD F2 , DC C5 DC 06 .
    P7BB19
        47 0F 09 2B 2B 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19z23
        47 0F 09 2B 2B 0F 15 5B 0F 19 , 0B , FD F4 , DC C5 DC 08 .
    P7Bb20
        47 0F 09 2B 2B 0F 16 , 09 , FD F2 , DC C5 DC 06 .
    P12B3
        47 0F 0E 2B 0F 05 , 08 , FD F1 , DC C5 DC 05 .
    P12B22
        47 0F 0E 2B 0F 18 , 08 , FD F1 , DC C5 DC 05 .
    

    โปรดทราบว่าคีย์การเรียงลำดับเป็นโครงสร้างในหลายฟิลด์คั่นด้วยเครื่องหมายจุลภาค แต่ละเขตข้อมูลจะต้องมีการเรียงลำดับอย่างอิสระเพื่อที่จะนำเสนอปัญหาเล็ก ๆ อีกปัญหาหนึ่งที่จะแก้ไขหากจำเป็นต้องใช้สิ่งนี้ใน SQL Server


**หากมีข้อสงสัยเกี่ยวกับประสิทธิภาพการใช้งานฟังก์ชั่นที่ผู้ใช้กำหนดโปรดทราบว่าวิธีการที่นำเสนอมีการใช้งานน้อยที่สุด ในความเป็นจริงเหตุผลหลักสำหรับการจัดเก็บค่าปกติคือเพื่อหลีกเลี่ยงการเรียก UDF ต่อแต่ละแถวของแต่ละแบบสอบถาม ในวิธีการหลัก UDF จะใช้เพื่อตั้งค่าของSortColumnและที่จะทำเฉพาะINSERTและUPDATEผ่านทริกเกอร์ การเลือกค่านั้นเป็นเรื่องธรรมดามากกว่าการแทรกและอัปเดตและค่าบางอย่างจะไม่อัปเดต สำหรับแต่ละSELECTเคียวรีที่ใช้SortColumnตัวกรองสำหรับช่วงในส่วนWHEREคำสั่ง UDF จำเป็นเพียงครั้งเดียวต่อค่า range_start และ range_end แต่ละค่าเพื่อรับค่าปกติ UDF ไม่ได้เรียกว่าต่อแถว

สำหรับ UDT นั้นการใช้งานนั้นเหมือนกับ scalar UDF ความหมายการแทรกและการอัพเดตจะเรียกเมธอดการทำให้เป็นมาตรฐานในแต่ละแถวเพื่อตั้งค่า จากนั้นวิธีการนอร์มัลไลซ์จะถูกเรียกหนึ่งครั้งต่อแบบสอบถามต่อแต่ละ range_start และ range_value ในตัวกรองช่วง แต่ไม่ใช่ต่อแถว

จุดสนับสนุนการฟื้นฟูใน SQLCLR UDF ก็คือว่ามันไม่ได้ทำการเข้าถึงข้อมูลใด ๆ และเป็นตัวกำหนดถ้ามันถูกทำเครื่องหมายว่าIsDeterministic = trueมันสามารถเข้าร่วมในแผนคู่ขนาน (ซึ่งอาจช่วยINSERTและUPDATEดำเนินการ) ในขณะที่ UDF ของ T-SQL จะป้องกันไม่ให้มีการใช้แผนขนาน

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