Numeri primi in un determinato intervallo


10

Di recente mi è stato affidato il compito di stampare tutti i numeri primi (1-100). Ho fallito drasticamente lì. Il mio codice:

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

Anche se ho finito senza completarlo, mi chiedo se sia possibile fare tali programmi su Database (SQL Server 2008 R2).

Se sì, come può finire.


2
Non togliermi nessuna delle risposte fornite, ma questo è il miglior articolo che ho visto sull'argomento: sqlblog.com/blogs/hugo_kornelis/archive/2006/09/23/…
Erik Darling

L'obiettivo è fare solo 1 - 100 o qualsiasi intervallo e 1 - 100 era solo un intervallo di esempio?
Solomon Rutzky,

Nella mia domanda, era da 1 a 100. Sarei bravo a ottenere un approccio generalista, quindi uno specifico.
ispostback,

Risposte:


11

Il modo più rapido e semplice per stampare "tutti i numeri primi (1-100)" è quello di abbracciare completamente il fatto che i numeri primi sono un insieme di valori noti, finiti e immutabili ("conosciuti" e "finiti" all'interno di un gamma particolare, ovviamente). Su questa scala ridotta, perché sprecare CPU ogni volta per calcolare un mucchio di valori che sono noti da molto tempo e non occupare quasi memoria per archiviare?

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)

Naturalmente, se è necessario calcolare i numeri primi tra 1 e 100, il seguente è abbastanza efficiente:

;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
                    );

Questa query verifica solo numeri dispari in quanto i numeri pari non saranno comunque primi. È anche specifico per l'intervallo 1 - 100.

Ora, se hai bisogno di un intervallo dinamico (simile a quello mostrato nel codice di esempio nella domanda), il seguente è un adattamento della query sopra che è ancora piuttosto efficiente (ha calcolato l'intervallo di 1 - 100.000 - 9592 voci - in poco meno di 1 secondo):

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
                    );

Il mio test (utilizzo SET STATISTICS TIME, IO ON;) mostra che questa query ha prestazioni migliori rispetto alle altre due risposte fornite (finora):

GAMMA: 1 - 100

Query      Logical Reads       CPU Milliseconds    Elapsed Milliseconds
-------    ----------------    ----------------    -----------------

Solomon      0                 0                   0
Dan        396                 0                   0
Martin     394                 0                   1

GAMMA: 1 - 10.000

Query      Logical Reads       CPU Milliseconds    Elapsed Milliseconds
-------    ----------------    ----------------    -----------------

Solomon        0                   47                170
Dan        77015                 2547               2559
Martin       n/a

GAMMA: 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

GAMMA: 99.900 - 100.000

NOTA : per eseguire questo test ho dovuto correggere un bug nel codice di Dan - @startnumnon è stato preso in considerazione nella query, quindi è sempre iniziato a 1. Ho sostituito la Dividend.num <= @endnumlinea con Dividend.num BETWEEN @startnum AND @endnum.

Query      Logical Reads       CPU Milliseconds    Elapsed Milliseconds
-------    ----------------    ----------------    -----------------

Solomon       0                   0                   1
Dan           0                 157                 158
Martin      n/a

GAMMA: 1 - 100.000 (test parziale parziale)

Dopo aver corretto la query di Dan per il test da 99.900 a 100.000, ho notato che non c'erano più letture logiche elencate. Quindi ho riprovato questo intervallo con quella correzione ancora applicata e ho scoperto che le letture logiche erano di nuovo sparite e i tempi erano leggermente migliori (e sì, lo stesso numero di righe è stato restituito).

Query      Logical Reads       CPU Milliseconds    Elapsed Milliseconds
-------    ----------------    ----------------    -----------------

Dan                0             179,594            180,096

Qual è lo scopo di ROW_NUMBER() OVER (ORDER BY (SELECT 1))? Non ROW_NUMBER() OVER ()sarebbe equivalente?
Lennart,

Hi @Lennart .Se si tenta di utilizzare OVER (), si otterrà il seguente errore: The function 'ROW_NUMBER' must have an OVER clause with ORDER BY.. E, con ORDER BY, non può essere una costante, quindi la sottoquery restituisce una costante.
Solomon Rutzky,

1
Grazie, non ero a conoscenza di questa limitazione nel server SQL. Ha un senso adesso
Lennart,

Perché se lo uso DECLARE @RangeStart INT = 999900, @RangeEnd INT = 1000000;funziona ma non appena lo imposto DECLARE @RangeStart INT = 9999999900, @RangeEnd INT = 10000000000;dice 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 Quell'errore sta dicendo che i tuoi valori sono al di fuori dell'intervallo di INT. Il valore massimo che INTpuò contenere è 2.147.483.647, che è inferiore al valore iniziale di 9.999.999.900. Si ottiene questo errore anche se si esegue solo il DECLARE. Puoi provare a cambiare i tipi di dati variabili per essere BIGINTe vedere come va. È possibile che siano necessarie altre modifiche minori per supportarlo. Per gli intervalli di tipi di dati, vedere: int, bigint, smallint e tinyint .
Solomon Rutzky,

7

Un modo semplice ma non molto efficiente per restituire i numeri primi nell'intervallo 2-100 (1 non è un numero primo) sarebbe

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);

Potresti anche materializzare i numeri 2-100 in una tabella e implementare il setaccio di Eratostene tramite ripetuti aggiornamenti o eliminazioni.


4

Mi chiedo se sia possibile fare tali programmi su Database

Sì, è fattibile ma non credo che T-SQL sia lo strumento giusto per il lavoro. Di seguito è riportato un esempio di approccio basato su set in T-SQL per questo problema.

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

Possiamo scrivere il codice seguente e funziona:

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

Sopra ho creato una procedura memorizzata per ottenere i numeri primi.

Per conoscere i risultati, eseguire la procedura memorizzata:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.