web-dev-qa-db-fra.com

Comment puis-je sélectionner des rangées d'une requête hiérarchique avec le niveau le plus bas?

J'ai une requête hiérarchique à Oracle 11gr2 qui retourne quelque chose comme ceci:

  • Parent (niveau 1)
    • Enfant (niveau 2)
      • Petit-enfant (niveau 3)
    • Enfant (niveau 2)
      • Petit-enfant (niveau 3)
      • Petit-enfant (niveau 3)
    • Enfant (niveau 2)

La requête que je voudrais écrire devrait obtenir toutes les lignes correspondant à un prédicat, au minimum; c'est-à-dire le plus proche du parent. Par exemple, si l'une des lignes d'enfant correspond au prédicat, elle devrait renvoyer à cette rangée, que les lignes de petits-enfants correspondent. Si plusieurs lignes d'enfants correspondent, il devrait les rendre tous, à nouveau, quelles que soient les rangées de petits-enfants. Si aucune ligne d'enfants ne correspond, il devrait renvoyer des rangées de petits-enfants qui correspondent, etc. (dans le système réel, j'ai beaucoup plus de trois niveaux, et beaucoup plus de rangées par niveau.)

Je suppose que cela est possible avec des fonctions analytiques, mais je ne suis pas sûr de savoir lequel utiliser ou comment l'intégrer dans ma requête. J'ai vu des problèmes similaires résolus à l'aide de min (level) keep (dense_rank last order by level), mais cela ne semble pas faire tout ce que je veux.

6
Allan Lewis

Si vous avez une requête hiérarchique qui produit l'arbre entier sous le nœud racine, qui a également une colonne level colonne calculée, vous pouvez l'envelopper dans une table dérivée ou la CTE et utiliser l'agrégat de la fenêtre:

WITH query AS
  ( SELECT <columns list>, level
  -- your query here
  ) ,
cte AS 
  ( SELECT <columns list>, level,
           MIN(the_level) OVER () AS min_level
    FROM query
    WHERE <conditions>
  )
SELECT *
FROM cte
WHERE min_level = level ;
3
ypercubeᵀᴹ

À mon esprit, la question est ambiguë: "le niveau minimum; c'est-à-dire la plus proche du parent" appliquer globalement (de sorte que tous les résultats ont le même niveau) ou à chaque sous-arbre?

Si le niveau minimum est par sous-arbre, vous pouvez profiter de la manière dont les requêtes hiérarchiques fonctionnent et arrêtent simplement de traverser des descendants lorsque votre condition est assortie:

create table foo(
  id integer primary key
, parent_id integer references foo
, bar char(1)
);
select 1,null,'A' from dual union all
select 2,1,'A' from dual union all
select 3,2,'B' from dual union all
select 4,1,'B' from dual union all
select 5,4,'B' from dual union all
select 6,4,'A' from dual union all
select 7,1,'B' from dual union all
select 8,7,'A' from dual union all
select 9,8,'A' from dual union all
select 10,9,'B' from dual;
select *
from foo
where bar='B';
[.____] id | Parent_id | Bar 
 -: | --------: | : - 
 3 | 2 | B 
 4 | 1 | B 
 5 | 4 | B 
 7 | 1 | B 
 10 | 9 | B 
select *
from foo
where bar='B'
start with parent_id is null
connect by parent_id=(prior id) and (prior bar)<>'B';
[.____] id | Parent_id | Bar 
 -: | --------: | : - 
 3 | 2 | B 
 4 | 1 | B 
 7 | 1 | B 

dbfiddle ici

Notez que la ligne avec id = 5 n'est pas dans le jeu de résultats - le connect by Donne des descendants de id = 4.

Si le niveau minimum est global, vous avez besoin d'une étape supplémentaire pour supprimer tous les résultats au minimum au niveau mondial:

with l as ( select foo.*, level lev
            from foo
            where bar='B'
            start with parent_id is null
            connect by parent_id=(prior id) and (prior bar)<>'B'
            order by level )
select * from l where lev=(select lev from l where rownum=1);
[.____] id | Parent_id | Bar | Lev 
 -: | --------: | :  -: 
 4 | 1 | B | 2 [.____] 7 | 1 | B | 2 [.____]

dbfiddle --- (ici