Số nguyên tố trong một phạm vi nhất định


10

Gần đây, tôi được giao một nhiệm vụ in tất cả các số nguyên tố (1-100). Tôi đã thất bại nặng nề ở đó. Mã của tôi:

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

Mặc dù tôi đã kết thúc mà không hoàn thành nó, tôi tự hỏi liệu có khả thi để thực hiện các chương trình như vậy trên Cơ sở dữ liệu (SQL Server 2008 R2) không.

Nếu có, làm thế nào nó có thể kết thúc.


2
Không bỏ qua bất kỳ câu trả lời nào được đưa ra, nhưng đây là bài viết hay nhất tôi từng thấy về chủ đề này: sqlblog.com/bloss/hugo_kornelis/archive/2006/09/23/
Erik Darling

Là mục tiêu chỉ làm 1 - 100, hay bất kỳ phạm vi nào và 1 - 100 chỉ là một phạm vi ví dụ?
Solomon Rutzky

Trong câu hỏi của tôi, nó là 1 đến 100. Tôi sẽ rất tốt để có được một cách tiếp cận tổng quát, sau đó là một cách cụ thể.
ispostback

Câu trả lời:


11

Cho đến nay, cách nhanh nhất và dễ nhất để in "tất cả các số nguyên tố (1-100)" là nắm bắt hoàn toàn thực tế rằng các số nguyên tố là một tập hợp các giá trị đã biết, hữu hạn và không thay đổi ("đã biết" và "hữu hạn" trong một phạm vi cụ thể, tất nhiên). Ở quy mô nhỏ này, tại sao mỗi lần lãng phí CPU để tính toán một loạt các giá trị đã được biết đến từ rất lâu và hầu như không chiếm bất kỳ bộ nhớ nào để lưu trữ?

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)

Tất nhiên, nếu bạn cần tính các số nguyên tố trong khoảng từ 1 đến 100, thì những điều sau đây khá hiệu quả:

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

Truy vấn này chỉ kiểm tra các số lẻ vì dù sao số chẵn sẽ không là số nguyên tố. Nó cũng đặc trưng cho phạm vi 1 - 100.

Bây giờ, nếu bạn cần một phạm vi động (tương tự như được hiển thị trong mã ví dụ trong câu hỏi), thì sau đây là một điều chỉnh của truy vấn ở trên vẫn khá hiệu quả (nó đã tính phạm vi từ 1 - 100.000 - 9592 các mục - chỉ trong chưa đầy 1 giây):

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

Thử nghiệm của tôi (sử dụng SET STATISTICS TIME, IO ON;) cho thấy truy vấn này thực hiện tốt hơn hai câu trả lời còn lại (cho đến nay):

THAY ĐỔI: 1 - 100

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

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

THAY ĐỔI: 1 - 10.000

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

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

THAY ĐỔI: 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

THAY ĐỔI: 99.900 - 100.000

LƯU Ý : Để chạy thử nghiệm này, tôi đã phải sửa một lỗi trong mã của Dan - @startnumkhông được đưa vào truy vấn để nó luôn bắt đầu 1. Tôi thay Dividend.num <= @endnumdòng bằng Dividend.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 (kiểm tra lại một phần)

Sau khi sửa câu hỏi của Dan cho bài kiểm tra 99.900 - 100.000, tôi nhận thấy rằng không có bài đọc logic nào được liệt kê. Vì vậy, tôi đã kiểm tra lại phạm vi này với bản sửa lỗi đó vẫn được áp dụng và thấy rằng các lần đọc logic đã biến mất và thời gian tốt hơn một chút (và vâng, cùng một số hàng đã được trả về).

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

Dan                0             179,594            180,096

Mục đích của là ROW_NUMBER() OVER (ORDER BY (SELECT 1))gì? Sẽ không ROW_NUMBER() OVER ()tương đương?
Lennart

Xin chào @Lennart. Nếu bạn cố gắng sử dụng OVER (), bạn sẽ gặp lỗi sau : The function 'ROW_NUMBER' must have an OVER clause with ORDER BY.. Và, với ORDER BY, nó không thể là một hằng số, do đó, truy vấn con trả về một hằng số.
Solomon Rutzky

1
Cảm ơn, tôi đã không nhận thức được giới hạn này trong máy chủ sql. Làm cho ý nghĩa ngay bây giờ
Lennart

Tại sao nếu tôi sử dụng DECLARE @RangeStart INT = 999900, @RangeEnd INT = 1000000;nó hoạt động nhưng ngay sau khi tôi đặt DECLARE @RangeStart INT = 9999999900, @RangeEnd INT = 10000000000;nó nói 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 Lỗi đó nói rằng các giá trị của bạn nằm ngoài phạm vi INT. Giá trị tối đa INTcó thể giữ là 2.147.483.647, nhỏ hơn giá trị bắt đầu của bạn là 9,999.999.900. Bạn nhận được lỗi đó ngay cả khi bạn thực hiện chỉ DECLARE. Bạn có thể thử thay đổi các kiểu dữ liệu biến thành BIGINTvà xem điều đó diễn ra như thế nào. Có thể là những thay đổi nhỏ khác sẽ cần thiết để hỗ trợ điều đó. Đối với phạm vi kiểu dữ liệu, vui lòng xem: int, bigint, smallint và tinyint .
Solomon Rutzky

7

Một cách đơn giản nhưng không hiệu quả để trả về các số nguyên tố trong phạm vi 2-100 (1 không phải là số nguyên tố) sẽ là

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

Bạn cũng có khả năng cụ thể hóa các số từ 2 đến 100 trong một bảng và thực hiện Sàng lọc Eratosthenes thông qua các cập nhật hoặc xóa lặp đi lặp lại.


4

Tôi tự hỏi là có khả thi để làm các chương trình như vậy trên cơ sở dữ liệu

Vâng, điều đó là khả thi nhưng tôi không nghĩ T-SQL là công cụ phù hợp cho công việc. Dưới đây là một ví dụ về cách tiếp cận dựa trên tập hợp trong T-SQL cho vấn đề này.

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

Chúng tôi có thể viết mã dưới đây và nó hoạt động:

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

Ở trên tôi đã tạo một Thủ tục lưu trữ để có được các số nguyên tố.

Để biết kết quả, hãy thực hiện thủ tục được lưu trữ:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.