โดยวิธีที่เร็วและง่ายที่สุดในการพิมพ์ "หมายเลขเฉพาะทั้งหมด (1-100)" คือการยอมรับความจริงที่ว่าจำนวนเฉพาะเป็นค่าที่ทราบ จำกัด และไม่มีการเปลี่ยนแปลงค่า ("ทราบ" และ "จำกัด " ภายใน แน่นอน) ที่ขนาดเล็กขนาดนี้ทำไมต้องเสีย CPU ในแต่ละครั้งเพื่อคำนวณค่าจำนวนมากซึ่งเป็นที่รู้จักกันมานานและใช้หน่วยความจำใด ๆ ในการจัดเก็บอย่างหนัก
SELECT tmp.[Prime]
FROM (VALUES (2), (3), (5), (7), (11), (13),
(17), (19), (23), (29), (31), (37), (41),
(43), (47), (53), (59), (61), (67), (71),
(73), (79), (83), (89), (97)) tmp(Prime)
แน่นอนถ้าคุณต้องการคำนวณจำนวนเฉพาะระหว่าง 1 ถึง 100 สิ่งต่อไปนี้มีประสิทธิภาพพอสมควร:
;WITH base AS
(
SELECT tmp.dummy, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS [num]
FROM (VALUES (0), (0), (0), (0), (0), (0), (0)) tmp(dummy)
), nums AS
(
SELECT (ROW_NUMBER() OVER (ORDER BY (SELECT 1)) * 2) + 1 AS [num]
FROM base b1
CROSS JOIN base b2
), divs AS
(
SELECT [num]
FROM base b3
WHERE b3.[num] > 4
AND b3.[num] % 2 <> 0
AND b3.[num] % 3 <> 0
)
SELECT given.[num] AS [Prime]
FROM (VALUES (2), (3)) given(num)
UNION ALL
SELECT n.[num] AS [Prime]
FROM nums n
WHERE n.[num] % 3 <> 0
AND NOT EXISTS (SELECT *
FROM divs d
WHERE d.[num] <> n.[num]
AND n.[num] % d.[num] = 0
);
ข้อความค้นหานี้ทดสอบเฉพาะเลขคี่เท่านั้นเนื่องจากตัวเลขจะไม่เป็นเอกสิทธิ์ นอกจากนี้ยังมีเฉพาะช่วง 1 - 100
ตอนนี้หากคุณต้องการช่วงไดนามิก (คล้ายกับที่แสดงในตัวอย่างโค้ดในคำถาม) ดังนั้นการปรับตัวของแบบสอบถามข้างต้นที่ยังค่อนข้างมีประสิทธิภาพ (คำนวณจากช่วง 1 - 100,000 - 9592 รายการ - ภายในไม่ถึง 1 วินาที):
DECLARE @RangeStart INT = 1,
@RangeEnd INT = 100000;
DECLARE @HowMany INT = CEILING((@RangeEnd - @RangeStart + 1) / 2.0);
;WITH frst AS
(
SELECT tmp.thing1
FROM (VALUES (0), (0), (0), (0), (0), (0), (0), (0), (0), (0)) tmp(thing1)
), scnd AS
(
SELECT 0 AS [thing2]
FROM frst t1
CROSS JOIN frst t2
CROSS JOIN frst t3
), base AS
(
SELECT TOP( CONVERT( INT, CEILING(SQRT(@RangeEnd)) ) )
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS [num]
FROM scnd s1
CROSS JOIN scnd s2
), nums AS
(
SELECT TOP (@HowMany)
(ROW_NUMBER() OVER (ORDER BY (SELECT 1)) * 2) +
(@RangeStart - 1 - (@RangeStart%2)) AS [num]
FROM base b1
CROSS JOIN base b2
), divs AS
(
SELECT [num]
FROM base b3
WHERE b3.[num] > 4
AND b3.[num] % 2 <> 0
AND b3.[num] % 3 <> 0
)
SELECT given.[num] AS [Prime]
FROM (VALUES (2), (3)) given(num)
WHERE given.[num] >= @RangeStart
UNION ALL
SELECT n.[num] AS [Prime]
FROM nums n
WHERE n.[num] BETWEEN 5 AND @RangeEnd
AND n.[num] % 3 <> 0
AND NOT EXISTS (SELECT *
FROM divs d
WHERE d.[num] <> n.[num]
AND n.[num] % d.[num] = 0
);
การทดสอบของฉัน (ใช้SET STATISTICS TIME, IO ON;
) แสดงให้เห็นว่าแบบสอบถามนี้ทำงานได้ดีกว่าอีกสองคำตอบที่ได้รับ (จนถึง):
ช่วง: 1 - 100
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Solomon 0 0 0
Dan 396 0 0
Martin 394 0 1
ช่วง: 1 - 10,000
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Solomon 0 47 170
Dan 77015 2547 2559
Martin n/a
ช่วง: 1 - 100,000
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Solomon 0 984 996
Dan 3,365,469 195,766 196,650
Martin n/a
ช่วง: 99,900 - 100,000
หมายเหตุ : เพื่อที่จะรันการทดสอบนี้ผมมีการแก้ไขข้อผิดพลาดในรหัสแดนของ - ไม่ได้เป็นปัจจัยหนึ่งในการค้นหาจึงมักจะเริ่มต้นที่@startnum
1
ฉันแทนที่สอดคล้องกับDividend.num <= @endnum
Dividend.num BETWEEN @startnum AND @endnum
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Solomon 0 0 1
Dan 0 157 158
Martin n/a
RANGE: 1 - 100,000 (ทดสอบซ้ำบางส่วน)
หลังจากแก้ไขแบบสอบถามของ Dan สำหรับการทดสอบ 99,900 - 100,000 ฉันสังเกตเห็นว่าไม่มีการอ่านเชิงตรรกะอีกต่อไป ดังนั้นฉันจึงทดสอบช่วงนี้อีกครั้งโดยใช้การแก้ไขนั้นและยังพบว่าการอ่านแบบลอจิคัลหายไปอีกครั้งและเวลาดีขึ้นเล็กน้อย (และใช่จำนวนแถวเดียวกันจะถูกส่งคืน)
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Dan 0 179,594 180,096