Страсти по "CONNECT BY"

Andrew Max
Дата: 28.07.2005 19:50:00
Прошу прощения, если очень похожая задачка обсуждалась - поиском не нашел.

Постановка проста.
Имеются таблички:

1. "Пользователи".
create table t_users(Nomer int, Text varchar2(20));
insert into t_users values (1, 'Max');
alter table t_users 
 add constraint pk_t_users primary key (Nomer);

2. "Группы пользователей".
create table t_usersgr(Nomer int, Text varchar2(20), Parent int);
insert into t_usersgr values (100, 'Group 1', null);
insert into t_usersgr values (200, 'Group 1-1', 100);
insert into t_usersgr values (300, 'Group 2', null);
insert into t_usersgr values (400, 'Group 3', 200);
alter table t_usersgr
 add constraint pk_t_usersgr primary key (Nomer);
Группы образуют иерархическую структуру, для этого, собственно, и нужно поле Parent. Считается, что если пользователь является членом некоторой группы, то он также является членом всех родительских групп.

3. "Членство в группах".
create table t_groupsm(Nomer int, NomerGr int);
insert into t_groupsm values (1, 200);
insert into t_groupsm values (1, 300);
alter table t_groupsm
 add constraint pk_t_groupsm primary key (Nomer, NomerGr);

Собственно, вопрос, наверное, уже ясен: необходимо написать запрос, который покажет членство пользователей в группах с учетом иерархии групп.

Лично мне пока не придумывается ничего, кроме как поступить так:
SQL> select distinct m.nomer, g.nomer as NomerGr
  2   from t_groupsm m,
  3   (
  4    select t.*, sys_connect_by_path(t.nomer, '\')  || '\' path
  5     from t_usersgr t
  6     connect by prior t.parent = t.nomer
  7   ) g
  8   where instr(g.path, '\' || to_char(m.nomergr) || '\') <=
  9    instr(g.path, '\' || to_char(g.nomer) || '\') and
 10    instr(g.path, '\' || to_char(m.nomergr) || '\') <> 0;

     NOMER    NOMERGR
---------- ----------
         1        100
         1        200
         1        300

Кто сможет подсказать, как это сделать еще элегантнее и проще - тому большое спасибо и много пива

Спасибо заранее.
Elic
Дата: 28.07.2005 20:32:27
select distinct m.nomer, g.nomer as NomerGr
  from t_groupsm m,
  ( select t.*, sys_connect_by_path(t.nomer, '\') || '\' as path
      from t_usersgr t
      start with t.nomer in (select m2.nomergr from t_groupsm m2)
      connect by t.nomer = prior t.parent
  ) g
  where g.path like '\' || to_char(m.nomergr) || '\%'
Andrew Max
Дата: 28.07.2005 20:52:13
2 Elic:
Спасибо большое.
Правда, START WITH ... похожий я уже догадался написать самостоятельно. Ибо без него нехорошо.

Похоже, лучших вариантов нет?

Тогда благодарю еще раз.

P.S. Интересно, а в каких количествах таможня пропускает пиво без сопроводительных документов? ;)
Andrew Max
Дата: 28.07.2005 22:13:17
Хм... подумал еще немного и засомневался: а стоит ли все-таки указывать это?
...
start with t.nomer in (select m2.nomergr from t_groupsm m2)
...

Дело в том, что в реальности количество групп, членом которых никто не является, будет стремиться к нулю.

Наверное, стоит потестировать оба варианта.
Еще раз спасибо, Elic.