I wonder if there is any way to generate Fibonacci numbers that beat in simplicity and efficiency this one I wrote:
WITH d (seq) AS
(SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL < 195)
SELECT seq
,fib
FROM d
MODEL
DIMENSION BY(seq)
MEASURES(0 AS fib)
RULES
(fib [1] = 0,
fib [2] = 1,
fib [seq BETWEEN 3 AND 194] = fib[CV(seq) - 2] + fib[CV(seq) - 1],
fib [seq > 194] = NULL)
ORDER BY 1
/
Execution Plan
----------------------------------------------------------
Plan hash value: 2245903385
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 2 (0)| 00:00:01 |
| 1 | SQL MODEL ORDERED | | 1 | 13 | | |
| 2 | VIEW | | 1 | 13 | 2 (0)| 00:00:01 |
|* 3 | CONNECT BY WITHOUT FILTERING| | | | | |
| 4 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter(LEVEL<195)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
4798 bytes sent via SQL*Net to client
500 bytes received via SQL*Net from client
14 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
194 rows processed
SQL>
Note: LEVEL < 195
was not arbitrarily chosen, higher values make the algorithm lose precision so I decided not to include them in order to keep correct results only.
on the simplicity side of things, the query can rely on the built in features (ITERATE ()
and ITERATION_NUMBER
) of MODEL
:
select * from dual
model
dimension by (0 seq)
measures (0 val)
rules iterate (195)
(
val[iteration_number] = val[iteration_number-1] + val[iteration_number-2],
val[2] = 1,
val[1] = 0,
val[0] = 0
)
;
You can use a recursive sub-query factoring clause:
WITH fib ( lvl, value, next ) AS (
SELECT 1, 0, 1
FROM DUAL
UNION ALL
SELECT lvl + 1, next, value + next
FROM fib
WHERE lvl < 195
)
SELECT lvl, value FROM fib
Something like this should be (much?) faster:
with
constants ( x, y, z ) as (
select 0.5 * ( 1 + sqrt(5) ),
0.5 * ( 1 - sqrt(5) ),
sqrt(5)
from dual
)
select level as seq, round( ( power(x, level - 1) - power(y, level - 1) ) / z ) as fib
from constants
connect by level < 195
;
The point being, you don't need to use the recursive formula; the terms can be written in closed form. Since computers can't do arithmetic with real numbers, only with rational number approximations, I needed to add a ROUND(...)
but even so this should be faster than recursive approaches.
EDIT: At the OP's request I traced the execution of this code. I don't see the recursive calls the OP is referring to in the Comment below.
Execution Plan
----------------------------------------------------------
Plan hash value: 1236776825
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
|* 1 | CONNECT BY WITHOUT FILTERING| | | | |
| 2 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(LEVEL<195)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
6306 bytes sent via SQL*Net to client
684 bytes received via SQL*Net from client
14 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
194 rows processed
EDIT #2
I suspect the simple generation of levels in a recursive query may be expensive. It's possible that a cross-join of similarly generated, but smaller sequences of integers may work a bit faster. The code looks more complicated (of course); the only change, though, is the way I generate the powers.
with
constants ( x, y, z ) as (
select 0.5 * ( 1 + sqrt(5) ),
0.5 * ( 1 - sqrt(5) ),
sqrt(5)
from dual
),
powers ( n ) as (
select 14 * a.p + b.q
from (select level - 1 p from dual connect by level <= 14) a
cross join
(select level - 1 q from dual connect by level <= 14) b
)
select n + 1 as seq, round( ( power(x, n) - power(y, n) ) / z ) as fib
from constants cross join powers
where n < 195
;
来源:https://stackoverflow.com/questions/44415801/any-better-fibonacci-series-generator-using-pure-oracle-sql