最近、すべての素数(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)でそのようなプログラムを実行することは可能でしょうか。
はいの場合、どのように終了する可能性があります。
printの最も迅速で簡単な方法は、「すべての素数(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;
を使用)は、このクエリが(これまでのところ)与えられた他の2つの回答よりも優れていることを示しています。
範囲:1-1
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Solomon 0 0 0
Dan 396 0 0
Martin 394 0 1
範囲:1-10,0
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Solomon 0 47 170
Dan 77015 2547 2559
Martin n/a
範囲:1-100,0
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,0
[〜#〜]注[〜#〜]:このテストを実行するには、Danのコードのバグを修正する必要がありました-@startnum
はクエリに含まれていなかったため、常に1
から開始されました。 Dividend.num <= @endnum
行をDividend.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(部分的な再テスト)
99,900-100,000テストに対するDanのクエリを修正した後、論理的な読み取りがリストされていないことに気付きました。そのため、この修正を適用したままこの範囲を再テストしたところ、論理読み取りが再びなくなり、時間がわずかに改善されたことがわかりました(そして、はい、同じ数の行が返されました)。
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Dan 0 179,594 180,096
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の数値を具体化して、更新または削除を繰り返して Sieve of Eratosthenes を実装することもできます。
データベースでそのようなプログラムを実行することは実行可能ですか?
はい、それは可能ですが、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
以下のコードを書くとうまくいきます:
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
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