给定范围内的质数


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/...
埃里克·达林

目标是只执行1-100还是任意范围,而1-100只是示例范围?
所罗门·鲁兹基

在我的问题中,这是1到100。我希望能得到一个通用的方法,然后是一个特定的方法。
ispostback

Answers:


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

注意:为了运行此测试,我不得不修复Dan代码中的错误- @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

范围: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 '18

@Lennart,您好。如果尝试使用OVER (),将会收到以下错误:The function 'ROW_NUMBER' must have an OVER clause with ORDER BY.。并且,使用ORDER BY,它不能是常量,因此子查询将返回常量。
所罗门·鲁茨基'18

1
谢谢,我不知道此限制在sql server中。现在说得通
Lennart '18

为什么我可以使用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.
弗朗切斯科·曼托瓦尼

1
@FrancescoMantovani该错误表示您的值超出的范围INT。该最大值INT可容纳为2,147,483,647,这比你9999999900初始值小。即使仅执行,也会出现该错误DECLARE。您可以尝试将变量数据类型更改为be,BIGINT然后看看如何进行。为了支持这一点,可能需要进行其他一些小的更改。有关数据类型范围,请参见:int,bigint,smallint和tinyint
所罗门·鲁兹基

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的数字,并通过重复更新或删除来实现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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.