Oracle 11g Release 2, clause WITH récursive et Fibonacci

Oracle 11.2 introduit la possibilité de créer une récursion dans la clause WITH d’un ordre SELECT. D’aucuns diront qu’Oracle ne fait qu’implémenter le standard pour éviter la syntaxe propriétaire avec sa clause « CONNECT ». En fait cette fonctionnalité permet également de résoudre des problèmes plus simplement. Cet article illustre cette nouveauté en proposant une solution pour créer ce qui est sans doute la plus célèbre des suites: la suite de Fibonacci.

Pour commencer…

Pour bien commencer, consultez la documentation et en particulier les sections qui suivent:

Comme vous pouvez le constater le premier exemple qui vient à l’esprit est le parcours d’arbre. Autrement dit, une requête que vous écrivez comme ceci jusqu’en 11.1:

col text format a40
col mgr format 9999
select rpad(' ',2*(level-1))||empno||': '||ename text,
       mgr
  from scott.emp
       connect by prior empno=mgr
       start with job='PRESIDENT'
 order siblings by ename;
TEXT                          MGR
----------------------------- -----
7839: KING
  7698: BLAKE                 7839
    7499: ALLEN               7698
    7900: JAMES               7698
    7654: MARTIN              7698
    7844: TURNER              7698
    7521: WARD                7698
  7782: CLARK                 7839
    7934: MILLER              7782
  7566: JONES                 7839
    7902: FORD                7566
      7369: SMITH             7902
    7788: SCOTT               7566
      7876: ADAMS             7788
Execution Plan
----------------------------------------------------------
Plan hash value: 763482334
-----------------------------------------------------------------------------
| Id  | Operation                               | Name | Rows  | Cost (%CPU)|
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |      |    14 |   4    (25)|
|*  1 |  CONNECT BY NO FILTERING WITH START-WITH|      |       |            |
|   2 |   TABLE ACCESS FULL                     | EMP  |    14 |   3     (0)|
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("MGR"=PRIOR "EMPNO")
       filter("JOB"='PRESIDENT')

En 11.2, vous pouvez donc obtenir le même résultat avec une autre syntaxe ET un plan d’exécution différent:

col text format a40
col mgr format 9999
with empl (empno, ename, xlevel, mgr) as
  (select empno, ename, 1, mgr
     from scott.emp
    where job='PRESIDENT'
   union all
   select e.empno, e.ename, empl.xlevel+1, e.mgr
     from scott.emp e, empl
    where e.mgr=empl.empno)
  search depth first by ename set ord
select rpad(' ',2*xlevel)||empno||': '||ename text,
       mgr
  from empl;
TEXT                       MGR
-------------------------  -----
  7839: KING
    7698: BLAKE             7839
      7499: ALLEN           7698
      7900: JAMES           7698
      7654: MARTIN          7698
      7844: TURNER          7698
      7521: WARD            7698
    7782: CLARK             7839
      7934: MILLER          7782
    7566: JONES             7839
      7902: FORD            7566
    7369: SMITH             7902
      7788: SCOTT           7566
    7876: ADAMS             7788
14 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3907725112
-----------------------------------------------------------------------------
| Id  | Operation                               | Name | Rows  | Cost (%CPU)|
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |      |    25 |    8   (25)|
|   1 |  VIEW                                   |      |    25 |    8   (25)|
|   2 |   UNION ALL (RECURSIVE WITH) DEPTH FIRST|      |       |            |
|*  3 |    TABLE ACCESS FULL                    | EMP  |     3 |    3    (0)|
|*  4 |    HASH JOIN                            |      |    22 |    4   (25)|
|   5 |     RECURSIVE WITH PUMP                 |      |       |            |
|*  6 |     TABLE ACCESS FULL                   | EMP  |    13 |    3    (0)|
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter("JOB"='PRESIDENT')
   4 - access("E"."MGR"="EMPL"."EMPNO")
   6 - filter("E"."MGR" IS NOT NULL)

Implémenter une suite géométrique

Mais sortons un peu de la documentation en implémentant une suite géométrique. Pour éviter les erreurs « ORA-01426: numeric overflow » ou « ORA-32044: cycle detected while executing recursive WITH query », vous pouvez limiter le nombre de récursions avec une colonne « depth » comme ci-dessous :

col val2 format 9999
with x (val2, depth) as
  (select 1, 1
     from dual
   union all
   select 2*val2, depth+1
     from x where
    depth<10)
select val2 from x;
 VAL2
-----
    1
    2
    4
    8
   16
   32
   64
  128
  256
  512

Implémenter la suite de Fibonacci

Vous pouvez également utiliser des récursions plus complexes qu’une simple multiplication d’une colonne. L’exemple ci-dessous implémente, par exemple, la suite de Fibonacci :

col val1 format 9999
with x (val1, val2, depth) as
  (select 1, 1, 1
     from dual
   union all
   select val1+val2, val1, depth+1
     from x
    where depth<10)
select val1 from x;
VAL1
----
   1
   2
   3
   5
   8
  13
  21
  34
  55
  89

Cette nouvelle extension syntaxique permet donc d’exprimer différemment certaines requêtes et en l’occurrence de référencer la ligne précédente simplement. Reste à apprécier l’intérêt pour vos applications; si vous voulez en savoir plus, lisez le très bon article de Luca Jellema sur le sujet. Et restez branché sur le blog pour d’autres exemples des nouveauté d’Oracle 11.2 dans les jours qui viennent. D’ici là, abonnez-vous au blog Easyteam par E-Mail, Twitter et RSS et soyez patient…