ตัวเลขเฉพาะในช่วงที่กำหนด


10

เมื่อเร็ว ๆ นี้ฉันได้รับมอบหมายให้พิมพ์หมายเลขเฉพาะทั้งหมด (1-100) ฉันล้มเหลวอย่างมากที่นั่น รหัสของฉัน:

Create Procedure PrintPrimeNumbers
@startnum int,
@endnum int
AS 
BEGIN
Declare @a INT;
Declare @i INT = 1
(
Select a = @startnum / 2;
WHILE @i<@a
BEGIN
@startnum%(@a-@i)
i=i+1;
)
END

แม้ว่าฉันจะลงเอยโดยไม่ทำให้เสร็จ แต่ฉันก็สงสัยว่าเป็นไปได้หรือไม่ที่จะทำโปรแกรมเช่นนั้นบนฐานข้อมูล (SQL Server 2008 R2)

ถ้าใช่มันจะจบได้อย่างไร


2
เพื่อไม่ให้ห่างจากคำตอบใด ๆ ที่ได้รับ แต่นี่เป็นบทความที่ดีที่สุดที่ฉันเคยเห็นในหัวข้อ: sqlblog.com/blogs/hugo_kornelis/archive/2006/09/23/…
Erik Darling

เป้าหมายของการทำ 1 - 100 หรือช่วงใด ๆ และ 1 - 100 เป็นเพียงช่วงตัวอย่างหรือไม่
โซโลมอน Rutzky

ในคำถามของฉันมันคือ 1 ถึง 100 ฉันจะดีที่จะได้รับแนวทางทั่วไปแล้วเป็นวิธีที่เฉพาะเจาะจง
ispostback

คำตอบ:


11

โดยวิธีที่เร็วและง่ายที่สุดในการพิมพ์ "หมายเลขเฉพาะทั้งหมด (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 <= @endnumDividend.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

มีจุดประสงค์ROW_NUMBER() OVER (ORDER BY (SELECT 1))อะไร? จะไม่ROW_NUMBER() OVER ()เทียบเท่า?
Lennart

สวัสดี @Lennart ถ้าคุณพยายามที่จะใช้งานคุณจะได้รับข้อผิดพลาดต่อไปนี้:OVER () The function 'ROW_NUMBER' must have an OVER clause with ORDER BY.และด้วยORDER BY, มันไม่สามารถเป็นค่าคงที่ได้, ดังนั้นเคียวรีย่อยจะส่งคืนค่าคงที่
โซโลมอน Rutzky

1
ขอบคุณฉันไม่ได้ตระหนักถึงข้อ จำกัด นี้ในเซิร์ฟเวอร์ sql ทำให้รู้สึกตอนนี้
Lennart

ทำไมถ้าฉันใช้DECLARE @RangeStart INT = 999900, @RangeEnd INT = 1000000;มันใช้งานได้ แต่ทันทีที่ฉันตั้งDECLARE @RangeStart INT = 9999999900, @RangeEnd INT = 10000000000;มันบอกว่าMsg 8115, Level 16, State 2, Line 1 Arithmetic overflow error converting expression to data type int. Msg 1014, Level 15, State 1, Line 5 A TOP or FETCH clause contains an invalid value.?
Francesco Mantovani

1
@FrancescoMantovani INTที่ผิดพลาดจะบอกว่าค่าของคุณอยู่นอกช่วงของ ค่าสูงสุดที่INTสามารถเก็บได้คือ 2,147,483,647 ซึ่งน้อยกว่าค่าเริ่มต้นของคุณที่ 9,999,999,900 DECLAREคุณจะได้รับข้อผิดพลาดที่แม้ว่าคุณจะดำเนินการเพียง คุณสามารถลองเปลี่ยนประเภทข้อมูลตัวแปรให้เป็นBIGINTและดูวิธีการที่จะไป เป็นไปได้ว่าจำเป็นต้องมีการเปลี่ยนแปลงเล็กน้อยอื่น ๆ เพื่อสนับสนุนสิ่งนั้น สำหรับช่วงประเภทข้อมูลโปรดดูที่: int, bigint, smallint และ TINYINT
โซโลมอน Rutzky

7

วิธีที่ง่าย แต่ไม่มีประสิทธิภาพมากในการส่งคืนหมายเลขเฉพาะในช่วง 2-100 (1 ไม่ใช่เฉพาะ) จะเป็น

WITH Ten AS (SELECT * FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) V(N)),
     Hundred(N) AS (SELECT T1.N * 10 + T2.N + 1 FROM Ten T1, Ten T2)
SELECT H1.N
FROM   Hundred H1
WHERE  H1.N > 1
       AND NOT EXISTS(SELECT *
                      FROM   Hundred H2
                      WHERE  H2.N > 1
                             AND H1.N > H2.N
                             AND H1.N % H2.N = 0);

นอกจากนี้คุณยังสามารถทำให้ตัวเลข 2-100 เป็นจริงในตารางและใช้Sieve of Eratosthenesผ่านการปรับปรุงหรือลบซ้ำ ๆ


4

ฉันสงสัยว่ามันเป็นไปได้ที่จะทำโปรแกรมดังกล่าวบนฐานข้อมูล

ใช่เป็นไปได้ แต่ฉันไม่คิดว่า T-SQL เป็นเครื่องมือที่เหมาะสมสำหรับงาน ด้านล่างนี้เป็นตัวอย่างของวิธีการตั้งค่าใน T-SQL สำหรับปัญหานี้

CREATE PROC dbo.PrintPrimeNumbers
    @startnum int,
    @endnum int
AS 
WITH 
     t4 AS (SELECT n FROM (VALUES(0),(0),(0),(0)) t(n))
    ,t256 AS (SELECT 0 AS n FROM t4 AS a CROSS JOIN t4 AS b CROSS JOIN t4 AS c CROSS JOIN t4 AS d)
    ,t16M AS (SELECT ROW_NUMBER() OVER (ORDER BY (a.n)) AS num FROM t256 AS a CROSS JOIN t256 AS b CROSS JOIN t256 AS c)
SELECT num
FROM t16M AS Dividend
WHERE
    Dividend.num <= @endnum
    AND NOT EXISTS(
        SELECT 1
        FROM t16M AS Divisor
        WHERE
            Divisor.num <= @endnum
            AND Divisor.num BETWEEN 2 AND SQRT(Dividend.num)
            AND Dividend.num % Divisor.num = 0
            AND Dividend.num <= @endnum
    );
GO
EXEC dbo.PrintPrimeNumbers 1, 100;
GO

0

เราสามารถเขียนโค้ดด้านล่างและใช้งานได้:

CREATE procedure sp_PrimeNumber(@number int)
as 
begin
declare @i int
declare @j int
declare @isPrime int
set @isPrime=1
set @i=2
set @j=2
while(@i<=@number)
begin
    while(@j<=@number)
    begin
        if((@i<>@j) and (@i%@j=0))
        begin
            set @isPrime=0
            break
        end
        else
        begin
            set @j=@j+1
        end
    end
    if(@isPrime=1)
    begin
        SELECT @i
    end
    set @isPrime=1
    set @i=@i+1
    set @j=2
end
end

ด้านบนฉันได้สร้างกระบวนงานที่เก็บไว้เพื่อรับหมายเลขเฉพาะ

หากต้องการทราบผลลัพธ์ให้ดำเนินการตามขั้นตอนที่เก็บไว้:

EXECUTE sp_PrimeNumber 100

0
DECLARE @UpperLimit INT, @LowerLimit INT

SET @UpperLimit = 500
SET @LowerLimit = 100

DECLARE @N INT, @P INT
DECLARE @Numbers TABLE (Number INT NULL)
DECLARE @Composite TABLE (Number INT NULL)

SET @P = @UpperLimit

IF (@LowerLimit > @UpperLimit OR @UpperLimit < 0 OR @LowerLimit < 0 )
    BEGIN
        PRINT 'Incorrect Range'
    END 
ELSE
    BEGIN
        WHILE @P > @LowerLimit
            BEGIN
                INSERT INTO @Numbers(Number) VALUES (@P)
                SET @N = 2
                WHILE @N <= @UpperLimit/2
                    BEGIN
                        IF ((@P%@N = 0 AND @P <> @N) OR (@P IN (0, 1)))
                            BEGIN
                                INSERT INTO @Composite(Number) VALUES (@P)
                                BREAK
                            END
                        SET @N = @N + 1
                    END
                SET @P = @P - 1
            END
        SELECT Number FROM @Numbers
        WHERE Number NOT IN (SELECT Number FROM @Composite)
        ORDER BY Number
        END
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.