ฉันจะแปลงจำนวนเต็มบวก 100 ล้านตัวแรกเป็นสตริงได้อย่างไร


13

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

ฉันจะพยายามเริ่มด้วยคำนิยามที่ค่อนข้างเป็นทางการ สตริงจะรวมอยู่ในชุดข้อมูลหากประกอบด้วยตัวอักษรพิมพ์ใหญ่จาก A - Z เทอมแรกของชุดคือ "A" ชุดประกอบด้วยสตริงที่ถูกต้องทั้งหมดเรียงตามความยาวก่อนและตามลำดับตัวอักษรทั่วไปที่สอง หากสายอยู่ในตารางในคอลัมน์ที่เรียกว่าSTRING_COLคำสั่งซื้อจะถูกกำหนดไว้ใน T-SQL ORDER BY LEN(STRING_COL) ASC, STRING_COL ASCเป็น

เพื่อให้คำจำกัดความที่เป็นทางการน้อยลงให้ดูที่ส่วนหัวคอลัมน์ตามตัวอักษรใน excel ชุดเป็นรูปแบบเดียวกัน ลองพิจารณาว่าคุณจะแปลงจำนวนเต็มเป็นฐาน 26 ได้อย่างไร:

1 -> A, 2 -> B, 3 -> C, ... , 25 -> Y, 26 -> Z, 27 -> AA, 28 -> AB, ...

การเปรียบเทียบไม่สมบูรณ์แบบเพราะ "A" ทำงานแตกต่างจาก 0 ในฐานสิบ ด้านล่างเป็นตารางค่าที่เลือกซึ่งหวังว่าจะทำให้ชัดเจนยิ่งขึ้น:

╔════════════╦════════╗
 ROW_NUMBER  STRING 
╠════════════╬════════╣
          1  A      
          2  B      
         25  Y      
         26  Z      
         27  AA     
         28  AB     
         51  AY     
         52  AZ     
         53  BA     
         54  BB     
      18278  ZZZ    
      18279  AAAA   
     475253  ZZZY   
     475254  ZZZZ   
     475255  AAAAA  
  100000000  HJUNYV 
╚════════════╩════════╝

เป้าหมายคือการเขียนSELECTแบบสอบถามที่ส่งกลับสตริง 100000000 แรกตามลำดับที่กำหนดไว้ข้างต้น ฉันทำการทดสอบโดยใช้การสืบค้นใน SSMS ด้วยชุดผลลัพธ์ที่ถูกลบไปแทนที่จะบันทึกลงในตาราง:

ทิ้งชุดผลลัพธ์

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

มีวิธีใดบ้างในการสร้างชุดข้อมูลที่อธิบายไว้ข้างต้นอย่างมีประสิทธิภาพ Martin Smithชี้ให้เห็นว่ากระบวนการจัดเก็บ CLR อาจไม่ใช่วิธีที่ดีเนื่องจากมีค่าใช้จ่ายในการประมวลผลจำนวนมาก

คำตอบ:


7

โซลูชันของคุณทำงานเป็นเวลา 35 วินาทีบนแล็ปท็อปของฉัน รหัสต่อไปนี้ใช้เวลา26 วินาที (รวมถึงการสร้างและเติมตารางชั่วคราว):

ตารางชั่วคราว

DROP TABLE IF EXISTS #T1, #T2, #T3, #T4;

CREATE TABLE #T1 (string varchar(6) NOT NULL PRIMARY KEY);
CREATE TABLE #T2 (string varchar(6) NOT NULL PRIMARY KEY);
CREATE TABLE #T3 (string varchar(6) NOT NULL PRIMARY KEY);
CREATE TABLE #T4 (string varchar(6) NOT NULL PRIMARY KEY);

INSERT #T1 (string)
VALUES
    ('A'), ('B'), ('C'), ('D'), ('E'), ('F'), ('G'),
    ('H'), ('I'), ('J'), ('K'), ('L'), ('M'), ('N'),
    ('O'), ('P'), ('Q'), ('R'), ('S'), ('T'), ('U'),
    ('V'), ('W'), ('X'), ('Y'), ('Z');

INSERT #T2 (string)
SELECT T1a.string + T1b.string
FROM #T1 AS T1a, #T1 AS T1b;

INSERT #T3 (string)
SELECT #T2.string + #T1.string
FROM #T2, #T1;

INSERT #T4 (string)
SELECT #T3.string + #T1.string
FROM #T3, #T1;

แนวคิดที่มีคือการเติมข้อมูลชุดค่าผสมที่สั่งไว้ล่วงหน้าซึ่งมีความยาวไม่เกินสี่อักขระ

รหัสหลัก

SELECT TOP (100000000)
    UA.string + UA.string2
FROM
(
    SELECT U.Size, U.string, string2 = '' FROM 
    (
        SELECT Size = 1, string FROM #T1
        UNION ALL
        SELECT Size = 2, string FROM #T2
        UNION ALL
        SELECT Size = 3, string FROM #T3
        UNION ALL
        SELECT Size = 4, string FROM #T4
    ) AS U
    UNION ALL
    SELECT Size = 5, #T1.string, string2 = #T4.string
    FROM #T1, #T4
    UNION ALL
    SELECT Size = 6, #T2.string, #T4.string
    FROM #T2, #T4
) AS UA
ORDER BY 
    UA.Size, 
    UA.string, 
    UA.string2
OPTION (NO_PERFORMANCE_SPOOL, MAXDOP 1);

นั่นคือการรวมคำสั่งการเก็บรักษาอย่างง่าย * ของตารางที่คำนวณล่วงหน้าสี่รายการพร้อมกับสตริง 5 อักขระและ 6 อักขระที่ได้รับตามต้องการ การแยกส่วนนำหน้าออกจากส่วนต่อท้ายเพื่อหลีกเลี่ยงการเรียงลำดับ

แผนการดำเนินการ

100 ล้านแถว


* ไม่มีอะไรใน SQL ข้างต้นที่ระบุการรวมการรักษาคำสั่งซื้อโดยตรง เครื่องมือเพิ่มประสิทธิภาพเลือกตัวดำเนินการทางกายภาพด้วยคุณสมบัติที่ตรงกับข้อกำหนดแบบสอบถาม SQL รวมถึงการสั่งซื้อระดับบนโดย ที่นี่จะเลือกการต่อข้อมูลที่ดำเนินการโดยการรวมการรวมตัวดำเนินการทางกายภาพเพื่อหลีกเลี่ยงการเรียงลำดับ

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


6

ฉันจะโพสต์คำตอบเพื่อเริ่มต้น ความคิดแรกของฉันคือควรใช้ประโยชน์จากลักษณะการวนรอบที่ซ้อนกันของการรวมกลุ่มพร้อมกับตารางตัวช่วยสองสามตัวที่มีหนึ่งแถวสำหรับแต่ละตัวอักษร ส่วนที่ซับซ้อนนั้นจะวนไปวนมาในลักษณะที่ผลลัพธ์เรียงตามความยาวรวมทั้งหลีกเลี่ยงการซ้ำซ้อน ตัวอย่างเช่นเมื่อข้ามการเข้าร่วม CTE ที่มีตัวอักษรใหญ่ทั้งหมด 26 ตัวพร้อมกับ '' คุณสามารถสร้างได้'A' + '' + 'A'และ'' + 'A' + 'A'เป็นสตริงตัวเดียวกัน

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

SELECT 'A'
UNION ALL SELECT 'B'
...
UNION ALL SELECT 'Y'
UNION ALL SELECT 'Z'

เมื่อเปรียบเทียบกับการใช้ CTE แบบสอบถามจะใช้เวลานานกว่า 3X ด้วยตารางคลัสเตอร์และ 4X อีกต่อไปเมื่อใช้ฮีป ฉันไม่เชื่อว่าปัญหาคือข้อมูลอยู่ในดิสก์ มันควรจะอ่านในหน่วยความจำเป็นหน้าเดียวและประมวลผลในหน่วยความจำสำหรับแผนทั้งหมด บางที SQL Server สามารถทำงานกับข้อมูลจากตัวดำเนินการสแกนอย่างมีประสิทธิภาพมากกว่าที่ทำได้กับข้อมูลที่เก็บไว้ในหน้าร้านแถวปกติ

ที่น่าสนใจ SQL Server เลือกที่จะนำผลลัพธ์ที่ได้รับคำสั่งจากหน้า tempdb หน้าเดียวที่มีข้อมูลที่สั่งไว้ในสปูลของตาราง:

สปอยเลอร์ที่ไม่ดี

SQL Server มักจะใส่ผลลัพธ์สำหรับตารางด้านในของ cross เข้าร่วมในสปูลตารางแม้ว่ามันจะดูไร้สาระก็ตาม ฉันคิดว่าเครื่องมือเพิ่มประสิทธิภาพต้องใช้งานเล็กน้อยในพื้นที่นี้ ฉันเรียกใช้แบบสอบถามด้วยNO_PERFORMANCE_SPOOLเพื่อหลีกเลี่ยงการเข้าชมที่มีประสิทธิภาพ

ปัญหาอย่างหนึ่งของการใช้ CTE ในการจัดเก็บข้อมูลตัวช่วยก็คือข้อมูลไม่รับประกันว่าจะถูกสั่งซื้อ ฉันไม่สามารถคิดได้ว่าทำไมเครื่องมือเพิ่มประสิทธิภาพจะเลือกที่จะไม่สั่งซื้อและในการทดสอบทั้งหมดของฉันข้อมูลจะถูกประมวลผลตามลำดับที่ฉันเขียน CTE:

ลำดับการสแกนอย่างต่อเนื่อง

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

(SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR)

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

แปลกแพง

น่าแปลกใจมากที่ฉันไม่สามารถสังเกตเห็นความแตกต่างอย่างมีนัยสำคัญทางสถิติในเวลา cpu หรือรันไทม์ที่มีหรือไม่มีการสั่งซื้อที่ชัดเจน หากมีสิ่งใดแบบสอบถามดูเหมือนว่าจะทำงานได้เร็วขึ้นด้วยORDER BY! ฉันไม่มีคำอธิบายสำหรับพฤติกรรมนี้

ส่วนที่ยุ่งยากของปัญหาคือการหาวิธีแทรกตัวอักษรที่ว่างเปล่าในสถานที่ที่เหมาะสม ตามที่กล่าวไว้ก่อนง่าย ๆCROSS JOINจะส่งผลให้ข้อมูลที่ซ้ำกัน เรารู้ว่าสตริงที่ 100000000 จะมีความยาวหกตัวอักษรเพราะ:

26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 = 914654 <100000000

แต่

26 + 26 ^ 2 + 26 ^ 3 + 26 ^ 4 + 26 ^ 5 + 26 ^ 6 = 321272406> 100000000

ดังนั้นเราจะต้องเข้าร่วมกับตัวอักษร CTE หกครั้งเท่านั้น สมมติว่าเราเข้าร่วมกับ CTE หกครั้งคว้าจดหมายฉบับหนึ่งจากแต่ละ CTE และเชื่อมต่อพวกเขาทั้งหมดเข้าด้วยกัน สมมติว่าตัวอักษรซ้ายสุดไม่ว่างเปล่า หากตัวอักษรใด ๆ ที่ตามมาว่างเปล่านั่นหมายความว่าสตริงนั้นมีความยาวน้อยกว่าหกตัวอักษรดังนั้นจึงเป็นตัวอักษรที่ซ้ำกัน ดังนั้นเราสามารถป้องกันการซ้ำซ้อนโดยค้นหาอักขระที่ไม่ว่างตัวแรกและต้องการให้ทุกตัวอักษรหลังจากนั้นจะต้องไม่ว่างเปล่า ฉันเลือกที่จะติดตามสิ่งนี้โดยกำหนดFLAGคอลัมน์ให้กับหนึ่งใน CTEs และเพิ่มการตรวจสอบให้กับWHEREข้อ ควรชัดเจนมากขึ้นหลังจากดูแบบสอบถาม แบบสอบถามสุดท้ายมีดังนี้:

WITH FIRST_CHAR (CHR) AS
(
    SELECT 'A'
    UNION ALL SELECT 'B'
    UNION ALL SELECT 'C'
    UNION ALL SELECT 'D'
    UNION ALL SELECT 'E'
    UNION ALL SELECT 'F'
    UNION ALL SELECT 'G'
    UNION ALL SELECT 'H'
    UNION ALL SELECT 'I'
    UNION ALL SELECT 'J'
    UNION ALL SELECT 'K'
    UNION ALL SELECT 'L'
    UNION ALL SELECT 'M'
    UNION ALL SELECT 'N'
    UNION ALL SELECT 'O'
    UNION ALL SELECT 'P'
    UNION ALL SELECT 'Q'
    UNION ALL SELECT 'R'
    UNION ALL SELECT 'S'
    UNION ALL SELECT 'T'
    UNION ALL SELECT 'U'
    UNION ALL SELECT 'V'
    UNION ALL SELECT 'W'
    UNION ALL SELECT 'X'
    UNION ALL SELECT 'Y'
    UNION ALL SELECT 'Z'
)
, ALL_CHAR (CHR, FLAG) AS
(
    SELECT '', 0 CHR
    UNION ALL SELECT 'A', 1
    UNION ALL SELECT 'B', 1
    UNION ALL SELECT 'C', 1
    UNION ALL SELECT 'D', 1
    UNION ALL SELECT 'E', 1
    UNION ALL SELECT 'F', 1
    UNION ALL SELECT 'G', 1
    UNION ALL SELECT 'H', 1
    UNION ALL SELECT 'I', 1
    UNION ALL SELECT 'J', 1
    UNION ALL SELECT 'K', 1
    UNION ALL SELECT 'L', 1
    UNION ALL SELECT 'M', 1
    UNION ALL SELECT 'N', 1
    UNION ALL SELECT 'O', 1
    UNION ALL SELECT 'P', 1
    UNION ALL SELECT 'Q', 1
    UNION ALL SELECT 'R', 1
    UNION ALL SELECT 'S', 1
    UNION ALL SELECT 'T', 1
    UNION ALL SELECT 'U', 1
    UNION ALL SELECT 'V', 1
    UNION ALL SELECT 'W', 1
    UNION ALL SELECT 'X', 1
    UNION ALL SELECT 'Y', 1
    UNION ALL SELECT 'Z', 1
)
SELECT TOP (100000000)
d6.CHR + d5.CHR + d4.CHR + d3.CHR + d2.CHR + d1.CHR
FROM (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d6
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d5
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d4
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d3
CROSS JOIN (SELECT TOP (27) FLAG, CHR FROM ALL_CHAR ORDER BY CHR) d2
CROSS JOIN (SELECT TOP (26) CHR FROM FIRST_CHAR ORDER BY CHR) d1
WHERE (d2.FLAG + d3.FLAG + d4.FLAG + d5.FLAG + d6.FLAG) =
    CASE 
    WHEN d6.FLAG = 1 THEN 5
    WHEN d5.FLAG = 1 THEN 4
    WHEN d4.FLAG = 1 THEN 3
    WHEN d3.FLAG = 1 THEN 2
    WHEN d2.FLAG = 1 THEN 1
    ELSE 0 END
OPTION (MAXDOP 1, FORCE ORDER, LOOP JOIN, NO_PERFORMANCE_SPOOL);

CTEs เป็นไปตามที่อธิบายไว้ข้างต้น ALL_CHARถูกรวมเข้ากับห้าครั้งเนื่องจากมีแถวสำหรับอักขระว่าง ตัวละครสุดท้ายในสตริงไม่ควรจะว่างเปล่าดังนั้น CTE FIRST_CHARแยกต่างหากที่กำหนดไว้สำหรับมัน คอลัมน์ค่าสถานะพิเศษในALL_CHARใช้เพื่อป้องกันการซ้ำซ้อนตามที่อธิบายไว้ข้างต้น อาจมีวิธีที่มีประสิทธิภาพมากกว่าในการตรวจสอบนี้ แต่มีวิธีที่ไม่มีประสิทธิภาพมากกว่าในการตรวจสอบ ความพยายามครั้งหนึ่งโดยฉันด้วยLEN()และPOWER()ทำให้คิวรีทำงานช้ากว่ารุ่นปัจจุบันถึงหกเท่า

MAXDOP 1และFORCE ORDERคำแนะนำที่เป็นสิ่งจำเป็นเพื่อให้แน่ใจว่าการสั่งซื้อจะถูกรักษาไว้ในแบบสอบถาม แผนประมาณหมายเหตุประกอบอาจมีประโยชน์เพื่อดูว่าทำไมการรวมอยู่ในลำดับปัจจุบัน:

คำอธิบายประกอบโดยประมาณ

แผนแบบสอบถามมักอ่านจากขวาไปซ้าย แต่คำขอแถวเกิดขึ้นจากซ้ายไปขวา ตามหลักการแล้ว SQL Server จะขอแถว 100 ล้านแถวจากd1ผู้ให้บริการการสแกนคงที่ ในขณะที่คุณย้ายจากซ้ายไปขวาฉันคาดว่าจะขอแถวน้อยลงจากผู้ให้บริการแต่ละราย เราสามารถมองเห็นได้ในแผนการดำเนินการที่เกิดขึ้นจริง นอกจากนี้ด้านล่างเป็นภาพหน้าจอจาก SQL Sentry Plan Explorer:

สำรวจ

เราได้ 100 ล้านแถวจาก d1 ซึ่งเป็นสิ่งที่ดี โปรดทราบว่าอัตราส่วนของแถวระหว่าง d2 และ d3 เกือบจะเท่ากับ 27: 1 (165336 * 27 = 4464072) ซึ่งสมเหตุสมผลถ้าคุณคิดว่า cross cross จะทำงานได้อย่างไร อัตราส่วนของแถวระหว่าง d1 และ d2 คือ 22.4 ซึ่งแสดงถึงงานที่สูญเปล่า ฉันเชื่อว่าแถวพิเศษนั้นมาจากการซ้ำซ้อน (เนื่องจากอักขระว่างอยู่ตรงกลางของสตริง) ซึ่งไม่ได้ผ่านโอเปอเรเตอร์การรวมลูปที่ซ้อนกันซึ่งทำหน้าที่กรอง

LOOP JOINคำใบ้ไม่จำเป็นเพราะในทางเทคนิคCROSS JOINเท่านั้นที่สามารถนำมาใช้เป็นห่วงเข้าร่วมใน SQL Server NO_PERFORMANCE_SPOOLคือการป้องกันไม่ให้มีการจัดตารางที่ไม่จำเป็น การข้ามคำใบ้สปูลทำให้ข้อความค้นหาใช้เวลานานขึ้น 3X บนเครื่องของฉัน

ข้อความค้นหาสุดท้ายมีเวลา cpu ประมาณ 17 วินาทีและเวลาที่ผ่านไปทั้งหมด 18 วินาที นั่นคือเมื่อเรียกใช้แบบสอบถามผ่าน SSMS และยกเลิกชุดผลลัพธ์ ฉันสนใจที่จะเห็นวิธีการอื่นในการสร้างข้อมูล


2

ฉันมีโซลูชันที่ปรับให้เหมาะสมเพื่อรับรหัสสตริงสำหรับหมายเลขเฉพาะใด ๆ สูงสุด 217,180,147,158 (8 ตัวอักษร) แต่ฉันไม่สามารถเอาชนะเวลาของคุณ:

บนเครื่องของฉันด้วย SQL Server 2014 การสืบค้นของคุณใช้เวลา 18 วินาทีในขณะที่การขุดของฉันใช้เวลา 3 เมตร 46 วินาที แบบสอบถามทั้งสองใช้การตั้งค่าสถานะการสืบค้นกลับที่ไม่มีเอกสาร 8690 เนื่องจาก 2014 ไม่สนับสนุนNO_PERFORMANCE_SPOOLคำใบ้

นี่คือรหัส:

/* precompute offsets and powers to simplify final query */
CREATE TABLE #ExponentsLookup (
    offset          BIGINT NOT NULL,
    offset_end      BIGINT NOT NULL,
    position        INTEGER NOT NULL,
    divisor         BIGINT NOT NULL,
    shifts          BIGINT NOT NULL,
    chars           INTEGER NOT NULL,
    PRIMARY KEY(offset, offset_end, position)
);

WITH base_26_multiples AS ( 
    SELECT  number  AS exponent,
            CAST(POWER(26.0, number) AS BIGINT) AS multiple
    FROM    master.dbo.spt_values
    WHERE   [type] = 'P'
            AND number < 8
),
num_offsets AS (
    SELECT  *,
            -- The maximum posible value is 217180147159 - 1
            LEAD(offset, 1, 217180147159) OVER(
                ORDER BY exponent
            ) AS offset_end
    FROM    (
                SELECT  exponent,
                        SUM(multiple) OVER(
                            ORDER BY exponent
                        ) AS offset
                FROM    base_26_multiples
            ) x
)
INSERT INTO #ExponentsLookup(offset, offset_end, position, divisor, shifts, chars)
SELECT  ofst.offset, ofst.offset_end,
        dgt.number AS position,
        CAST(POWER(26.0, dgt.number) AS BIGINT)     AS divisor,
        CAST(POWER(256.0, dgt.number) AS BIGINT)    AS shifts,
        ofst.exponent + 1                           AS chars
FROM    num_offsets ofst
        LEFT JOIN master.dbo.spt_values dgt --> as many rows as resulting chars in string
            ON [type] = 'P'
            AND dgt.number <= ofst.exponent;

/*  Test the cases in table example */
SELECT  /*  1.- Get the base 26 digit and then shift it to align it to 8 bit boundaries
            2.- Sum the resulting values
            3.- Bias the value with a reference that represent the string 'AAAAAAAA'
            4.- Take the required chars */
        ref.[row_number],
        REVERSE(SUBSTRING(REVERSE(CAST(SUM((((ref.[row_number] - ofst.offset) / ofst.divisor) % 26) * ofst.shifts) +
            CAST(CAST('AAAAAAAA' AS BINARY(8)) AS BIGINT) AS BINARY(8))),
            1, MAX(ofst.chars))) AS string
FROM    (
            VALUES(1),(2),(25),(26),(27),(28),(51),(52),(53),(54),
            (18278),(18279),(475253),(475254),(475255),
            (100000000), (CAST(217180147158 AS BIGINT))
        ) ref([row_number])
        LEFT JOIN #ExponentsLookup ofst
            ON ofst.offset <= ref.[row_number]
            AND ofst.offset_end > ref.[row_number]
GROUP BY
        ref.[row_number]
ORDER BY
        ref.[row_number];

/*  Test with huge set  */
WITH numbers AS (
    SELECT  TOP(100000000)
            ROW_NUMBER() OVER(
                ORDER BY x1.number
            ) AS [row_number]
    FROM    master.dbo.spt_values x1
            CROSS JOIN (SELECT number FROM master.dbo.spt_values WHERE [type] = 'P' AND number < 676) x2
            CROSS JOIN (SELECT number FROM master.dbo.spt_values WHERE [type] = 'P' AND number < 676) x3
    WHERE   x1.number < 219
)
SELECT  /*  1.- Get the base 26 digit and then shift it to align it to 8 bit boundaries
            2.- Sum the resulting values
            3.- Bias the value with a reference that represent the string 'AAAAAAAA'
            4.- Take the required chars */
        ref.[row_number],
        REVERSE(SUBSTRING(REVERSE(CAST(SUM((((ref.[row_number] - ofst.offset) / ofst.divisor) % 26) * ofst.shifts) +
            CAST(CAST('AAAAAAAA' AS BINARY(8)) AS BIGINT) AS BINARY(8))),
            1, MAX(ofst.chars))) AS string
FROM    numbers ref
        LEFT JOIN #ExponentsLookup ofst
            ON ofst.offset <= ref.[row_number]
            AND ofst.offset_end > ref.[row_number]
GROUP BY
        ref.[row_number]
ORDER BY
        ref.[row_number]
OPTION (QUERYTRACEON 8690);

เคล็ดลับที่นี่คือ precompute ซึ่งการเรียงสับเปลี่ยนที่แตกต่างกันเริ่มต้น:

  1. เมื่อคุณต้องส่งออกตัวอักษรเดี่ยวคุณจะมีพีชคณิต 26 ^ 1 ที่เริ่มต้นที่ 26 ^ 0
  2. เมื่อคุณต้องเอาต์พุต 2 chars คุณจะมีพีชคณิต 26 ^ 2 ที่เริ่มต้นที่ 26 ^ 0 + 26 ^ 1
  3. เมื่อคุณต้องส่งออก 3 ตัวอักษรคุณมีพีชคณิต 26 ^ 3 ที่เริ่มต้นที่ 26 ^ 0 + 26 ^ 1 + 26 ^ 2
  4. ทำซ้ำสำหรับ n chars

เคล็ดลับอื่น ๆ ที่ใช้คือการใช้ผลรวมเพื่อเข้าถึงค่าที่ถูกต้องแทนที่จะพยายามต่อกัน เพื่อให้บรรลุสิ่งนี้ฉันเพียงแค่ชดเชยตัวเลขจากฐาน 26 ถึงฐาน 256 และเพิ่มค่า ascii ของ 'A' สำหรับแต่ละหลัก ดังนั้นเราจึงได้รับการเป็นตัวแทนไบนารีของสตริงที่เรากำลังมองหา หลังจากนั้นการจัดการสตริงจะทำให้กระบวนการเสร็จสมบูรณ์


-1

ตกลงไปที่นี่สคริปต์ล่าสุดของฉันไป

ไม่มีวนซ้ำไม่มีการวนซ้ำ

ใช้งานได้เพียง 6 ตัว

ข้อเสียเปรียบที่ใหญ่ที่สุดคือใช้เวลาประมาณ 22 นาทีสำหรับ 1,00,00,000

เวลานี้สคริปต์ของฉันสั้นมาก

SET NoCount on

declare @z int=26
declare @start int=@z+1 
declare @MaxLimit int=10000000

SELECT TOP (@MaxLimit) IDENTITY(int,1,1) AS N
    INTO NumbersTest1
    FROM     master.dbo.spt_values x1   
   CROSS JOIN (SELECT number FROM master.dbo.spt_values WHERE [type] = 'P' AND number < 500) x2
            CROSS JOIN (SELECT number FROM master.dbo.spt_values WHERE [type] = 'P' AND number < 500) x3
    WHERE   x1.number < 219
ALTER TABLE NumbersTest1 ADD CONSTRAINT PK_NumbersTest1 PRIMARY KEY CLUSTERED (N)


select N, strCol from NumbersTest1
cross apply
(
select 
case when IntCol6>0 then  char((IntCol6%@z)+64) else '' end 
+case when IntCol5=0 then 'Z' else isnull(char(IntCol5+64),'') end 
+case when IntCol4=0 then 'Z' else isnull(char(IntCol4+64),'') end 
+case when IntCol3=0 then 'Z' else isnull(char(IntCol3+64),'') end 
+case when IntCol2=0 then 'Z' else isnull(char(IntCol2+64),'') end 
+case when IntCol1=0 then 'Z' else isnull(char(IntCol1+64),'') end strCol
from
(
select  IntCol1,IntCol2,IntCol3,IntCol4
,case when IntCol5>0 then  IntCol5%@z else null end IntCol5

,case when IntCol5/@z>0 and  IntCol5%@z=0 then  IntCol5/@z-1 
when IntCol5/@z>0 then IntCol5/@z
else null end IntCol6
from
(
select IntCol1,IntCol2,IntCol3
,case when IntCol4>0 then  IntCol4%@z else null end IntCol4

,case when IntCol4/@z>0 and  IntCol4%@z=0 then  IntCol4/@z-1 
when IntCol4/@z>0 then IntCol4/@z
else null end IntCol5
from
(
select IntCol1,IntCol2
,case when IntCol3>0 then  IntCol3%@z else null end IntCol3
,case when IntCol3/@z>0 and  IntCol3%@z=0 then  IntCol3/@z-1 
when IntCol3/@z>0 then IntCol3/@z
else null end IntCol4

from
(
select IntCol1
,case when IntCol2>0 then  IntCol2%@z else null end IntCol2
,case when IntCol2/@z>0 and  IntCol2%@z=0 then  IntCol2/@z-1 
when IntCol2/@z>0 then IntCol2/@z
else null end IntCol3

from
(
select case when N>0 then N%@z else null end IntCol1
,case when N%@z=0 and  (N/@z)>1 then (N/@z)-1 else  (N/@z) end IntCol2 

)Lv2
)Lv3
)Lv4
)Lv5
)LV6

)ca

DROP TABLE NumbersTest1

ดูเหมือนว่าตารางที่ได้รับจะถูกแปลงเป็นสเกลาร์คำนวณเดียวซึ่งมีรหัสมากกว่า 400,000 ตัวอักษร ฉันสงสัยว่ามีค่าใช้จ่ายในการคำนวณจำนวนมาก คุณอาจต้องการลองทำสิ่งที่คล้ายกับต่อไปนี้: dbfiddle.uk/… รู้สึกอิสระที่จะรวมส่วนประกอบของสิ่งนั้นเข้ากับคำตอบของคุณ
โจ Obbish
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.