Liczby pierwsze w danym zakresie

10

Ostatnio otrzymałem zadanie wydrukowania wszystkich liczb pierwszych (1-100). Tam drastycznie mi się nie udało. Mój kod:

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

Chociaż nie udało mi się go ukończyć, zastanawiam się, czy jest możliwe wykonywanie takich programów w bazie danych (SQL Server 2008 R2).

Jeśli tak, jak to się skończy.

ispostback
źródło
2
Nie należy odbierać żadnej z udzielonych odpowiedzi, ale jest to najlepszy artykuł, jaki widziałem na ten temat: sqlblog.com/blogs/hugo_kornelis/archive/2006/09/23/...
Erik Darling
Czy celem jest wykonanie 1 - 100, czy jakikolwiek zakres, a 1 - 100 to tylko przykładowy zakres?
Solomon Rutzky,
W moim pytaniu było to od 1 do 100. Byłoby dobrze, gdybyśmy przyjęli podejście ogólne, a następnie konkretne.
ispostback 11.09.16

Odpowiedzi:

11

Zdecydowanie najszybszym i najłatwiejszym sposobem na wydrukowanie „wszystkich liczb pierwszych (1-100)” jest pełne uwzględnienie faktu, że liczby pierwsze są znanym, skończonym i niezmiennym zestawem wartości („znanym” i „skończonym” w obrębie oczywiście określony zakres). Na tak małą skalę, po co marnować procesor za każdym razem, aby obliczyć wiązkę wartości, które są znane od bardzo dawna i nie zajmują prawie żadnej pamięci do przechowywania?

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)

Oczywiście, jeśli musisz obliczyć liczby pierwsze od 1 do 100, następujące działania są dość skuteczne:

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

To zapytanie sprawdza tylko liczby nieparzyste, ponieważ liczby parzyste i tak nie będą pierwsze. Jest również specyficzny dla zakresu od 1 do 100.

Teraz, jeśli potrzebujesz zakresu dynamicznego (podobnego do tego, który pokazano w przykładowym kodzie w pytaniu), to poniżej znajduje się dostosowanie powyższego zapytania, które jest nadal dość wydajne (obliczyło zakres 1 - 100 000 - 9592 wpisy - w niecałe 1 sekundę):

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

Moje testowanie (używanie SET STATISTICS TIME, IO ON;) pokazuje, że to zapytanie działa lepiej niż pozostałe dwie odpowiedzi (jak dotąd):

ZAKRES: 1 - 100

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

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

ZAKRES: 1 - 10 000

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

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

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

ZAKRES: 99 900 - 100 000

UWAGA : Aby uruchomić ten test, musiałem naprawić błąd w kodzie Dana - @startnumnie został uwzględniony w zapytaniu, więc zawsze zaczynał się od 1. Zamieniłem Dividend.num <= @endnumlinię na Dividend.num BETWEEN @startnum AND @endnum.

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

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

ZAKRES: 1 - 100 000 (częściowy ponowny test)

Po naprawieniu zapytania Dana dla testu 99,900 - 100 000 zauważyłem, że nie ma już żadnych logicznych odczytów. Ponownie przetestowałem ten zakres z wciąż stosowaną poprawką i stwierdziłem, że logiczne odczyty znowu zniknęły, a czasy były nieco lepsze (i tak, zwrócono tę samą liczbę wierszy).

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

Dan                0             179,594            180,096
Solomon Rutzky
źródło
Jaki jest cel ROW_NUMBER() OVER (ORDER BY (SELECT 1))? Nie ROW_NUMBER() OVER ()byłby równoważny?
Lennart
Cześć @Lennart .Przy próbie użycia OVER (), otrzymasz następujący błąd: The function 'ROW_NUMBER' must have an OVER clause with ORDER BY.. I, z ORDER BY, nie może być stałą, stąd podkwerenda, aby zwrócić stałą.
Solomon Rutzky
1
Dzięki, nie byłem świadomy tego ograniczenia na serwerze SQL. Ma to teraz sens
Lennart,
Dlaczego, jeśli DECLARE @RangeStart INT = 999900, @RangeEnd INT = 1000000;go używam , działa, ale gdy tylko DECLARE @RangeStart INT = 9999999900, @RangeEnd INT = 10000000000;go ustawię, mówi 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 Ten błąd mówi, że twoje wartości są poza zakresem INT. Maksymalna wartość, którą INTmożna przechowywać, wynosi 2 147 483 647, czyli mniej niż początkowa wartość 9 999 999 900. Ten błąd występuje, nawet jeśli wykonasz tylko DECLARE. Możesz spróbować zmienić zmienne typy danych BIGINTi sprawdzić, jak to działa . Możliwe jest, że w celu wsparcia tego będą potrzebne inne niewielkie zmiany. Aby uzyskać zakresy typów danych, zobacz: int, bigint, smallint i tinyint .
Solomon Rutzky
7

Byłby to prosty, ale niezbyt skuteczny sposób zwracania liczb pierwszych w zakresie 2-100 (1 nie jest liczbą pierwszą)

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

Możesz także potencjalnie zmaterializować liczby 2-100 w tabeli i zaimplementować sito Eratostenesa poprzez powtarzające się aktualizacje lub usuwanie.

Martin Smith
źródło
4

Zastanawiam się, czy możliwe jest wykonywanie takich programów w bazie danych

Tak, jest to wykonalne, ale nie sądzę, aby T-SQL był odpowiednim narzędziem do tego zadania. Poniżej znajduje się przykład podejścia opartego na zestawie w języku T-SQL dla tego problemu.

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
Dan Guzman
źródło
0

Możemy napisać poniższy kod i to działa:

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

Powyżej utworzyłem Procedurę składowaną w celu uzyskania liczb pierwszych.

Aby poznać wyniki, wykonaj procedurę przechowywaną:

EXECUTE sp_PrimeNumber 100
użytkownik156742
źródło
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
Manoj
źródło