{"id":626,"date":"2009-07-23T00:00:00","date_gmt":"2009-07-23T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/celkos-summer-sql-stumpers-prime-numbers\/"},"modified":"2021-09-29T16:22:05","modified_gmt":"2021-09-29T16:22:05","slug":"celkos-summer-sql-stumpers-prime-numbers","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/celkos-summer-sql-stumpers-prime-numbers\/","title":{"rendered":"Celko&#8217;s Summer SQL Stumpers: Prime Numbers"},"content":{"rendered":"<div id=\"PRETTY\">\n<h1><b>A PRIME SQL PUZZLE<\/b><\/h1>\n<p>I was teaching SQL classes for YAPC-10 (&#8220;Yet Another PERL Conference&#8221; #10) at Carnegie Mellon University at the end of June 2009.&#160; For the record, I have never used PERL and had to Google up an overview before I went; it is a very different creature from SQL.&#160; <\/p>\n<p>One of my students asked if you could write an SQL statement to generate the prime numbers less than 1000 (or any other limit) that scales well.&#160; He was bothered by the lack of loops in SQL and a Prime Number sieve is a common PERL programming exercise.&#160; You can Google it and see an animation at <a href=\"http:\/\/www.hbmeyer.de\/eratosiv.htm\">Eratosthenes&#8217; sieve<\/a> and some PERL code at <a href=\"http:\/\/www.perlmonks.org\/?node_id=276103.\">Sieve of Eratosthenes with closures<\/a> <\/p>\n<p>My immediate answer was &#8220;sure, but you might have to use a recursive CTE to replace the loop.&#160; Later I realized that was a really bad answer; you don&#8217;t need recursion, just a little math.&#160; There are two useful facts from Number Theory:<\/p>\n<ol>\n<li>The prime factors of a given number (n) cannot be greater than ceiling (&#226;n).&#160; Think about it; by definition (&#226;n * &#226;n)) = n, and by definition, ceiling (&#226;n) &gt;= floor(&#226;n) so integer rounding up will be safe.&#160; This says that if I look at (a * b = c) where (a &lt; b), then I don&#8217;t have to look at (b * a = c), so I can start searching for prime factors with small values.&#160;  <\/li>\n<li>All primes are of the form (6 * n &#194;&#177; 1), but not all number of that form are Primes.&#160; For example (n = 1) gives us {5, 7} and they are both primes.&#160; But for (n = 4) gives us {23, 25} where (25 = 5 * 5).&#160; What this does is remove the multiples of 2 and 3 from consideration. <\/li>\n<\/ol>\n<p>Let&#8217;s get all of that into SQL statements.&#160; Let&#8217;s start with a table for the primes: <\/p>\n<pre>CREATE TABLE Primes\n(p INTEGER NOT NULL PRIMARY KEY\n&#160;&#160;CHECK (p &gt; 1)); <\/pre>\n<p>Now, your puzzle is to fill the table up to some limit, say 1000 just to keep it simple.&#160; <\/p>\n<h1><b>&#160;ANSWERS:<\/b><\/h1>\n<p>Let&#8217;s assume we have a table named Sequence with integers from 1 to (n) that we can use.&#160; This is a common SQL programming idiom, so you don&#8217;t have to feel bad about using it. <\/p>\n<pre>CREATE TABLE Sequence \n(seq INTEGER NOT NULL PRIMARY KEY\nCHECK (seq &#160;&gt; 0));<\/pre>\n<p>There are lots of ways of filling this table, but here is one I like: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql highlight:0 decode:true\">WITH Digits(i)\nAS (SELECT i\n&#160;&#160; FROM (VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (0)) AS X(i))\nINSERT INTO Sequence(seq)\nSELECT (D3.i * 1000 + D2.i * 100 + D1.i * 10 + D0.i + 1) AS seq\n&#160;&#160;&#160;&#160;FROM Digits AS D0, Digits AS D1, Digits AS D2, Digits AS D3;<\/pre>\n<p>This template is easy to extend and the &#8220;.. + 1&#8221; gets rid of the zero.&#160; <\/p>\n<h2><b>ANSWER #1<\/b><\/h2>\n<p>For the first attempt, let&#8217;s load the Primes table with candidate numbers using math fact #2 from above.&#160; <\/p>\n<pre>INSERT INTO Primes (p) \n(SELECT (6 * seq) + 1\n&#160;&#160;FROM Sequence\nWHERE (6 * seq) + 1 &lt;= 1000\nUNION ALL \nSELECT (6 * seq) - 1\n&#160;&#160;FROM Sequence\nWHERE (6 * seq) + 1 &lt;= 1000);<\/pre>\n<p>An improvement which gets rid of the UNION ALL uses a table constant: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql highlight:0 decode:true\">INSERT INTO Primes (p) \nSELECT (6 * seq) + S.switch\n&#160;&#160;FROM Sequence\n&#160;&#160;&#160;&#160;&#160;&#160;CROSS JOIN\n&#160;&#160;&#160;&#160;&#160; (SELECT switch\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM (VALUES (-1), (+1))\n&#160;&#160;&#160;&#160;&#160;&#160; AS F(switch))S \n&#160;&#160;WHERE (6 * seq) + 1 &lt;= 1000;<\/pre>\n<p>Now we have too many rows in Primes and need to remove the non-primes.&#160; Now math fact #1 can come into play; test the set of numbers less than the square root to see if there is a factor among them.&#160; <\/p>\n<pre>DELETE FROM Primes\nWHERE EXISTS\n&#160;&#160;(SELECT * \n&#160;&#160;&#160;&#160; FROM Primes AS P1\n&#160;&#160;&#160;&#160;WHERE P1.p &lt;= CEILING (SQRT (Primes.p))\n&#160;&#160;&#160;&#160;&#160;&#160;AND (Primes.p % P1.p) = 0);<\/pre>\n<h2><b>ANSWER #2<\/b><\/h2>\n<p>Another way to load the candidates into Primes is to have the first few known primes hardwired into a query.&#160; This is a generalization of the math fact #2, which dealt with multiples of only 2 and 3.&#160; <\/p>\n<pre>INSERT INTO Primes (p) \nSELECT seq\n&#160;&#160;FROM Sequence\n&#160;&#160;WHERE 0 NOT IN (seq % 2, seq % 3, seq % 5, seq % 7, .. ); <\/pre>\n<p>The idea is that if we can limit the candidate set for Primes, performance will improve.&#160; At the extreme, if the list of <code>\"MOD (seq, &lt;prime&gt;)\"<\/code> expressions goes to a value equal or higher than the upper limit we are looking at, we get the answer immediately.<\/p>\n<p>This is a good trick; many SQL programmers think that an IN() list can only be constants.&#160; You might also want to look at how many values it can hold -It is larger than you think.&#160; <\/p>\n<p>Another candidate pruning trick is based on the math fact that integers with final digits {2, 4, 6, 8, 0} are even numbers; those with final digits {5, 0} are multiples of five. Let&#8217;s not look at them when we build a candidate table.<\/p>\n<pre>WITH Digits(i)\nAS (SELECT i\n&#160;&#160; FROM (VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (0)) AS X(i)\n&#160;&#160; )\nINSERT INTO Sequence(seq)\nSELECT (D3.i * 1000 + D2.i * 100 + D1.i * 10 + Units.i)\n&#160;&#160;FROM (SELECT i\n&#160;&#160; FROM (VALUES (1), (3), (7), (9)) AS X(i)) AS Units, \n&#160;&#160;&#160;&#160;&#160;&#160; Digits AS D1, Digits AS D2, Digits AS D3<\/pre>\n<\/p>\n<h2><b>ANSWER #3<\/b><\/h2>\n<p>Another approach is to generate all of the non-primes and remove them from the Sequence table.<\/p>\n<pre>INSERT INTO Primes (p) \n(SELECT seq FROM Sequence WHERE seq &lt;= 1000)\nEXCEPT\n(SELECT (F1.seq * F2.seq) AS composite_nbr\n&#160;&#160;FROM Sequence AS F1, Sequence AS F2\nWHERE F1.seq BETWEEN 2 AND CEILING (SQRT (1000)) \n&#160;&#160;AND F2.seq BETWEEN 2 AND CEILING (SQRT (1000))\n&#160;&#160;AND F1.seq &lt;= F2.seq\n&#160;&#160;AND (F1.seq * F2.seq) &lt;= 1000) <\/pre>\n<p>Obviously, the Sequence table in the left hand clause could be anyone of the trimmed candidate tables we previously constructed.&#160;&#160; <\/p>\n<p>What answers to do you have? As a hint, there are faster but more complicated algorithms, like the Sieve of Atkin and the various Wheel Sieves.<\/p>\n<p class=\"NOTE\">We have attached files containing code that runs in SQL 200 SQL 2005 and SQL 2008. Joe&#8217;s code in the article &#160;has been fixed to run in SQL 2008.<\/p>\n<p>The best answer to each stumper will be given a prize of a $100 Amazon voucher. The stumper will be run simultaneously on&#160; SQL Server Central and Simple-Talk. To see all the comments so far, you will need to visit both sites. We will take entries for a week after the first Monday of publication,&#160; posted as comments to the articles. Two weeks after the challenge is sent out, the judge&#8217;s decision and comments will be sent out in the newsletter, and published on the site.<\/p>\n<p>Joe Celko and Phil Factor will judge the answers to this puzzle. Your answer should : <br \/>&#160;&#160; 1) Solve the problem <i>&#8212; Duh! <\/i><br \/>&#160;&#160; 2) Avoid proprietary features in SQL Server that will not port or be good across <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; all releases, present and future. <br \/>&#160;&#160; 3) Use Standard features in SQL Server that will be good across all releases, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; present and future. Extra points for porting code. <br \/>&#160;&#160; 4) Be clever but not obscure. <br \/>&#160;&#160; 5) Explain how you got your answer. &#160;<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Joe Celko kicks off our series of Summer SQL Stumpers with a challenge to improve on his solution to\u00a0calculating the prime numbers between 1 and 10000. Once the various solutions have been contributed and judged, the winner will be announced. The competition will be run on Simple-Talk and SQL Server Central together.<br \/>\n&hellip;<\/p>\n","protected":false},"author":96214,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[4996,4150,4151,4252],"coauthors":[],"class_list":["post-626","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-joe-celko-example-sample-code-sql-free","tag-sql","tag-sql-server","tag-t-sql-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/626","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/96214"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=626"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/626\/revisions"}],"predecessor-version":[{"id":40133,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/626\/revisions\/40133"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=626"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=626"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=626"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=626"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}