Récemment, on m'a confié la tâche d'imprimer tous les nombres premiers (1-100). J'ai échoué radicalement là-bas. Mon code:
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
Bien que je me sois retrouvé sans le terminer, je me demande s'il est possible de faire de tels programmes sur la base de données (SQL Server 2008 R2).
Si oui, comment cela peut se terminer.
De loin, le moyen le plus rapide et le plus simple d’imprimer "tous les nombres premiers (1-100)" consiste à adopter pleinement le fait que les nombres premiers sont connus. , ensemble fini et immuable de valeurs ("connues" et "finies" dans une plage particulière, bien sûr). À cette petite échelle, pourquoi gaspiller le CPU à chaque fois pour calculer un tas de valeurs connues depuis très longtemps et prendre peu de mémoire pour stocker?
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)
Bien sûr, si vous devez calculer les nombres premiers entre 1 et 100, ce qui suit est assez efficace:
;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
);
Cette requête ne teste que les nombres impairs car les nombres pairs ne seront de toute façon pas premiers. Il est également spécifique à la plage de 1 à 100.
Maintenant, si vous avez besoin d'une plage dynamique (similaire à ce qui est montré dans l'exemple de code dans la question), alors ce qui suit est une adaptation de la requête ci-dessus qui est encore assez efficace (elle a calculé la plage de 1 - 100 000 - 9592 entrées - en un peu moins d'une seconde):
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
);
Mes tests (en utilisant SET STATISTICS TIME, IO ON;
) montre que cette requête fonctionne mieux que les deux autres réponses données (jusqu'à présent):
PLAGE: 1 - 1
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Solomon 0 0 0
Dan 396 0 0
Martin 394 0 1
PLAGE: 1 - 10 0
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Solomon 0 47 170
Dan 77015 2547 2559
Martin n/a
PLAGE: 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
GAMME: 99 900 - 100 0
[~ # ~] note [~ # ~] : Pour exécuter ce test, j'ai dû corriger un bogue dans le code de Dan - @startnum
n'a pas été pris en compte dans la requête, elle a donc toujours commencé à 1
. J'ai remplacé le Dividend.num <= @endnum
aligner avec Dividend.num BETWEEN @startnum AND @endnum
.
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Solomon 0 0 1
Dan 0 157 158
Martin n/a
PLAGE: 1 - 100 000 (re-test partiel)
Après avoir corrigé la requête de Dan pour le test 99,900 - 100,000, j'ai remarqué qu'il n'y avait plus de lectures logiques répertoriées. J'ai donc retesté cette plage avec ce correctif toujours appliqué et j'ai constaté que les lectures logiques avaient à nouveau disparu et que les temps étaient légèrement meilleurs (et oui, le même nombre de lignes a été renvoyé).
Query Logical Reads CPU Milliseconds Elapsed Milliseconds
------- ---------------- ---------------- -----------------
Dan 0 179,594 180,096
Un moyen simple mais peu efficace de renvoyer les nombres premiers compris entre 2 et 100 (1 n'est pas premier) serait
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);
Vous pouvez également potentiellement matérialiser les nombres 2-100 dans un tableau et implémenter le Tamis d'Eratosthène via des mises à jour ou des suppressions répétées.
Je me demande s'il est possible de faire de tels programmes sur la base de données
Oui, c'est faisable mais je ne pense pas que T-SQL soit le bon outil pour le travail. Voici un exemple d'une approche basée sur des ensembles dans T-SQL pour ce problème.
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
Nous pouvons écrire le code ci-dessous et cela fonctionne:
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
Ci-dessus, j'ai créé une procédure stockée pour obtenir des nombres premiers.
Pour connaître les résultats, exécutez la procédure stockée:
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