Belirli bir aralıktaki asal sayılar


10

Son zamanlarda, tüm asal sayıları (1-100) yazdırma görevi verildi. Orada büyük ölçüde başarısız oldum. Kodum:

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

Tamamlamadan bitirmiş olmama rağmen, bu tür programları Veritabanı'nda (SQL Server 2008 R2) yapmak mümkün olduğunu merak ediyorum.

Evet ise, nasıl bitebilir.


2
Verilen cevaplardan herhangi birini almak değil, ama bu konuda gördüğüm en iyi makale: sqlblog.com/blogs/hugo_kornelis/archive/2006/09/23/…
Erik Darling

Hedef sadece 1 - 100 mü, herhangi bir aralık mı ve 1 - 100 sadece örnek bir aralık mı?
Solomon Rutzky

Benim sorum, 1 ile 100 arasındaydı. Genel bir yaklaşım, sonra belirli bir yaklaşım elde etmek iyi olur.
ispostback

Yanıtlar:


11

"Tüm asal sayıları (1-100)" yazdırmanın en hızlı ve en kolay yolu , asal sayıların bilinen bir, sonlu ve değişmeyen bir değer kümesi ("bilinen" ve "sonlu") tabii ki belirli bir aralık). Bu kadar küçük bir ölçekte, neden çok uzun zamandır bilinen bir grup değeri hesaplamak ve saklamak için neredeyse hiç bellek kullanmamak için CPU'yu her seferinde boşa harcıyorsunuz?

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)

Tabii ki, 1 ile 100 arasındaki asal sayıları hesaplamanız gerekiyorsa, aşağıdakiler oldukça etkilidir:

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

Bu sorgu yalnızca tek sayıları test eder, çünkü çift sayılar yine de asal olmaz. Ayrıca 1 - 100 aralığına özgüdür.

Şimdi, dinamik bir aralığa ihtiyacınız varsa (sorudaki örnek kodda gösterilene benzer), aşağıdaki sorgu hala oldukça verimli olan bir uyarlamadır (1 - 100.000 - 9592 aralığını hesapladı) girişler - 1 saniyenin biraz altında):

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

Testlerim (kullanarak SET STATISTICS TIME, IO ON;), bu sorgunun verilen (şimdiye kadar) diğer iki cevaptan daha iyi performans gösterdiğini gösteriyor:

ARALIK: 1-100

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

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

MENZİL: 1 - 10.000

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

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

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

MENZİL: 99.900 - 100.000

NOT : Bu testi çalıştırmak için ben Dan kodunda bir hata düzeltmek zorunda - @startnumher zaman başladı bu yüzden sorgu içine çarpanlı değildi 1. Dividend.num <= @endnumHattı ile değiştirdim Dividend.num BETWEEN @startnum AND @endnum.

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

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

ARALIK: 1-100.000 (kısmi yeniden test)

Dan'ın 99.900 - 100.000 test sorgusunu düzelttikten sonra, listelenen daha mantıklı okuma olmadığını fark ettim. Bu aralığı hala uygulanan bu düzeltme ile tekrar test ettim ve mantıksal okumaların tekrar gittiğini ve zamanların biraz daha iyi olduğunu gördüm (ve evet, aynı sayıda satır döndürüldü).

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

Dan                0             179,594            180,096

Amaç nedir ROW_NUMBER() OVER (ORDER BY (SELECT 1))? ROW_NUMBER() OVER ()Eşdeğer olmaz mıydı ?
Lennart

Merhaba @Lennart .Eğer kullanmak girişiminde OVER ()aşağıdaki hatayı alırsınız: The function 'ROW_NUMBER' must have an OVER clause with ORDER BY.. Ve, ORDER BYbir sabit olamaz, bu nedenle alt sorgu bir sabiti döndürür.
Solomon Rutzky

1
Teşekkürler, sql sunucusunda bu sınırlama farkında değildi. Şimdi mantıklı
Lennart

Neden kullanırsam DECLARE @RangeStart INT = 999900, @RangeEnd INT = 1000000;çalışır ama ayarladığım DECLARE @RangeStart INT = 9999999900, @RangeEnd INT = 10000000000;anda der ki 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 Bu hata, değerlerinizin aralığının dışında olduğunu söylüyor INT. INTTutabilecek maksimum değer 2.147.483.647'dir; bu, başlangıç ​​değeriniz 9,999,999,900'den küçüktür. Sadece çalıştırsanız bile bu hatayı alıyorsunuz DECLARE. Değişken veri türlerini değiştirmeyi deneyebilir BIGINTve bunun nasıl gittiğini görebilirsiniz. Bunu desteklemek için başka küçük değişikliklere ihtiyaç duyulabilir. Veri türü aralıkları için lütfen bakınız: int, bigint, smallint ve tinyint .
Solomon Rutzky

7

2-100 (1 asal değildir) aralığındaki asal sayıları döndürmenin basit ama çok etkili olmayan bir yolu

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

Ayrıca 2-100 sayılarını bir tabloda potansiyel olarak gerçekleştirebilir ve Eratosthenes Eleği'ni tekrarlanan güncellemeler veya silmelerle uygulayabilirsiniz .


4

Acaba bu tür programları Veritabanı üzerinde yapmak mümkün mü

Evet, uygulanabilir ancak T-SQL'in iş için doğru araç olduğunu düşünmüyorum. Aşağıda bu sorun için T-SQL'de set tabanlı bir yaklaşım örneği verilmiştir.

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

Aşağıdaki kodu yazabiliriz ve çalışır:

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

Yukarıda asal sayıları elde etmek için bir Saklı Yordam oluşturdum.

Sonuçları bilmek için saklı yordamı yürütün:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.