用SQL计算100以内的质数
蛮有意思的一段SQL,拿来看一下。
以前写过一篇文章,描述如何使用PL/SQL来计算100以内的质数,今天重翻那篇文章的时候,突然想到,能不能用SQL来实现同样的功能。
其实这个功能用PLSQL实现最简单,思路也很清晰,判断一个数是否是质数,只需要检查这个数能否被比它小的质数整除就可以了。但是这种操作通过SQL来实现就十分的困难,因此这里通过另外一种方式来实现这个功能——构造。
通过查询100以内的数,去掉所有的合数,剩下的就是质数了:
SQL> WITH T
2 AS
3 (SELECT ROWNUM RN FROM DUAL CONNECT BY LEVEL < 100)
4 SELECT RN FROM T
5 WHERE RN > 1
6 MINUS
7 SELECT A.RN * B.RN FROM T A, T B
8 WHERE A.RN <= B.RN
9 AND A.RN > 1
10 AND B.RN > 1;
RN
----------
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
25 rows selected.
但是这种方法的效率明显要比PL/SQL的效率要低,如果取10000以内的质数:
SQL> SET TIMING ON
SQL> SET AUTOT TRACE STAT
SQL> WITH T
2 AS
3 (SELECT ROWNUM RN FROM DUAL CONNECT BY LEVEL < 10000)
4 SELECT RN FROM T
5 WHERE RN > 1
6MINUS
7 SELECT A.RN * B.RN FROM T A, T B
8 WHERE A.RN <= B.RN
9 AND A.RN > 1
10AND B.RN > 1;
1229 rows selected.
Elapsed: 00:02:41.54
Statistics
----------------------------------------------------------
511 recursive calls
81 db block gets
180002 consistent gets
65131 physical reads
648 redo size
17139 bytes sent via SQL*Net to client
1276 bytes received via SQL*Net from client
83 SQL*Net roundtrips to/from client
2 sorts (memory)
1 sorts (disk)
1229 rows processed
这个SQL执行了2分半种以上,当然这个SQL还可以进行一下优化,比如A的取值可以小于10000的开方,而B的取值可以小于10000除以最小的质数:
SQL> WITH T
2 AS
3 (SELECT ROWNUM RN FROM DUAL CONNECT BY LEVEL < 10000)
4 SELECT RN FROM T
5 WHERE RN > 1
6 MINUS
7 SELECT A.RN * B.RN FROM T A, T B
8 WHERE A.RN <= B.RN
9AND A.RN > 1
10 AND A.RN <= 100
11 AND B.RN > 1
12 AND B.RN <= 5000;
1229 rows selected.
Elapsed: 00:00:00.78
Statistics
----------------------------------------------------------
2 recursive calls
23 db block gets
1820 consistent gets
16 physical reads
692 redo size
17139 bytes sent via SQL*Net to client
1276 bytes received via SQL*Net from client
83 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
1229 rows processed
优化后,SQL的执行效率提高了3个数量级,其实这个SQL仍然是可以优化的,考虑除了2以外,所有的质数均为奇数:
SQL> WITH T
2 AS
3 (SELECT ROWNUM * 2 + 1 RN FROM DUAL CONNECT BY LEVEL < 4999)
4 SELECT 2 FROM DUAL
5 UNION ALL
6 (
7 SELECT RN FROM T
8 WHERE RN > 1
9 MINUS
10 SELECT A.RN * B.RN FROM T A, T B
11 WHERE A.RN <= B.RN
12 AND A.RN > 1
13 AND A.RN <= 100
14 AND B.RN > 1
15 AND B.RN <= 5000
16 )
17 ;
1229 rows selected.
Elapsed: 00:00:00.25
Statistics
----------------------------------------------------------
2 recursive calls
15 db block gets
512 consistent gets
8 physical reads
648 redo size
17138 bytes sent via SQL*Net to client
1276 bytes received via SQL*Net from client
83 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
1229 rows processed
虽然通过SQL优化已经将极大的提高了效率,但是和PLSQL比,效率仍然相去甚远:
SQL> DECLARE
2 TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3 V_RESULT T_RECORD;
4 I NUMBER DEFAULT 3;
5 N NUMBER DEFAULT 0;
6 BEGIN
7 --DBMS_OUTPUT.PUT_LINE(2);
8 V_RESULT(1) := 3;
9 WHILE(I < 10000) LOOP
10 FOR J IN 1..V_RESULT.COUNT LOOP
11 IF V_RESULT(J) * V_RESULT(J) > I THEN
12 --DBMS_OUTPUT.PUT_LINE(I);
13 V_RESULT(V_RESULT.COUNT + 1) := I;
14 EXIT;
15 END IF;
16 IF TRUNC(I/V_RESULT(J)) = I/V_RESULT(J) THEN
17 EXIT;
18END IF;
19 END LOOP;
20 IF N = 2 THEN
21 I := I + 4;
22 N := 1;
23 ELSE
24 I := I + 2;
25 N := N + 1;
26 END IF;
27 END LOOP;
28 V_RESULT(0) := 2;
29 END;
30 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.04
这说明使用SQL并不一定就是最佳选择,有的时候使用PLSQL效率反而会更高。使用合适的方法做适合的事情,一味追求使用单条SQL实现很可能会损失性能。