Как колонку таблицы записать в строку? Иерархия !

dbstalker, 23 мая

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

Предлагаю следующий метод с использованием иерархических запросов и функции SYS_CONNECT_BY_PATH. Если у вас есть лучший способ, то предложите. Всегда рады сотрудничеству.

Есть такая таблица:

create table dbst_tab (id number(2))
в ней такие строчки

ID
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
выполним такой запрос
select id,lag(id) over (order by id ) as id_1 from dbst_TAB
получим такой результат
ID|ID_1
1|null
2|1
3|2
4|3
5|4
6|5
7|6
8|7
9|8
10|9
11|10
12|11
13|12
14|13
15|14
16|15
17|16
18|17
19|18
20|19
а теперь
select sys_connect_by_path(id, ',') as "ID"
   from ( select id,lag(id) over (order by id ) as id_1 from dbst_TAB)
   start with id_1 is null
   connect by id_1 = prior id
результат будет таковым
ID
,1
,1,2
,1,2,3
,1,2,3,4
,1,2,3,4,5
,1,2,3,4,5,6
,1,2,3,4,5,6,7
,1,2,3,4,5,6,7,8
,1,2,3,4,5,6,7,8,9
,1,2,3,4,5,6,7,8,9,10
,1,2,3,4,5,6,7,8,9,10,11
,1,2,3,4,5,6,7,8,9,10,11,12
,1,2,3,4,5,6,7,8,9,10,11,12,13
,1,2,3,4,5,6,7,8,9,10,11,12,13,14
,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17
,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18
,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19
,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20
Заберем ненужный значок
select ltrim(sys_connect_by_path(id, ','),',') as "ID"
   from ( select id,lag(id) over (order by id ) as id_1 from dbst_TAB)
   start with id_1 is null
   connect by id_1 = prior id
ID
1
1,2
1,2,3
1,2,3,4
1,2,3,4,5
1,2,3,4,5,6
1,2,3,4,5,6,7
1,2,3,4,5,6,7,8
1,2,3,4,5,6,7,8,9
1,2,3,4,5,6,7,8,9,10
1,2,3,4,5,6,7,8,9,10,11
1,2,3,4,5,6,7,8,9,10,11,12
1,2,3,4,5,6,7,8,9,10,11,12,13
1,2,3,4,5,6,7,8,9,10,11,12,13,14
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20
осталось только взять последнюю строку. Сделаем это так
select * from (select ltrim(sys_connect_by_path(id, ','),',') as "ID"
      from ( select id,lag(id) over (order by id ) as id_1 from dbst_TAB)
      start with id_1 is null
      connect by id_1 = prior id
      order by 1 desc)
      where rownum=1
получаем строку
ID
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20
это и есть ответ на заданный вопрос. А теперь возьмём немного сложнее табличку и выполним все по аналогии:
CREATE TABLE DBST_TABL
 (CITY    VARCHAR2(20 BYTE),
  NAME    VARCHAR2(30 BYTE),
  PERIOD  VARCHAR2(6 BYTE),
  GOODS   VARCHAR2(6 BYTE));
insert into  dbst_tabl (city,name,period,goods) values ('c1','n1','200601','t0');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n1','200601','t1');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n1','200601','t2');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n1','200601','t3');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n1','200602','t1');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n1','200602','t2');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n1','200603','t3');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n1','200603','t4');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n1','200603','t5');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n2','200601','t0');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n2','200601','t2');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n2','200601','t3');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n2','200601','t4');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n4','200603','t3');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n4','200603','t4');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n4','200603','t5');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n4','200604','t1');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n4','200604','t2');
insert into  dbst_tabl (city,name,period,goods) values ('c1','n4','200604','t3');
insert into  dbst_tabl (city,name,period,goods) values ('c2','n2','200601','t0');
insert into  dbst_tabl (city,name,period,goods) values ('c2','n2','200601','t2');
insert into  dbst_tabl (city,name,period,goods) values ('c2','n2','200601','t3');
insert into  dbst_tabl (city,name,period,goods) values ('c2','n2','200601','t4');
insert into  dbst_tabl (city,name,period,goods) values ('c2','n3','200601','t5');
insert into  dbst_tabl (city,name,period,goods) values ('c2','n3','200602','t1');
insert into  dbst_tabl (city,name,period,goods) values ('c2','n3','200602','t2');
insert into  dbst_tabl (city,name,period,goods) values ('c2','n4','200603','t3');
insert into  dbst_tabl (city,name,period,goods) values ('c3','n4','200603','t4');
insert into  dbst_tabl (city,name,period,goods) values ('c3','n4','200603','t5');
insert into  dbst_tabl (city,name,period,goods) values ('c3','n4','200604','t1');
insert into  dbst_tabl (city,name,period,goods) values ('c3','n4','200604','t2');
начнем с такого запроса
select city,name, period,goods formm,
lag(goods) over (partition by city,name,period order by goods ) as form_prev 
from dbst_tabl
получим результат
CITY|NAME|PERIOD|FORMM|FORM_PREV
c1|n1|200601|t0|null|
c1|n1|200601|t1|t0|
c1|n1|200601|t2|t1|
c1|n1|200601|t3|t2|
c1|n1|200602|t1|null|
c1|n1|200602|t2|t1|
c1|n1|200603|t3|null|
c1|n1|200603|t4|t3|
c1|n1|200603|t5|t4|
c1|n2|200601|t0|null|
c1|n2|200601|t2|t0|
c1|n2|200601|t3|t2|
c1|n2|200601|t4|t3|
c1|n4|200603|t3|null|
c1|n4|200603|t4|t3|
c1|n4|200603|t5|t4|
c1|n4|200604|t1|null|
c1|n4|200604|t2|t1|
c1|n4|200604|t3|t2|
c2|n2|200601|t0|null|
c2|n2|200601|t2|t0|
c2|n2|200601|t3|t2|
c2|n2|200601|t4|t3|
c2|n3|200601|t5|null|
c2|n3|200602|t1|null|
c2|n3|200602|t2|t1|
c2|n4|200603|t3|null|
c3|n4|200603|t4|null|
c3|n4|200603|t5|t4|
c3|n4|200604|t1|null|
c3|n4|200604|t2|t1|
select city,name,period,sys_connect_by_path(formm, ',') formms,length(sys_connect_by_path(formm, ','))len
      from 
(  
select city,name, period,goods formm,
lag(goods) over (partition by city,name,period order by goods ) as form_prev 
from dbst_tabl 
)
start with form_prev is null
connect by  city=prior city and name=prior name and period=prior period and form_prev=prior formm
CITY|NAME|PERIOD|FORMMS|LEN
c1|n1|200601|,t0|3|
c1|n1|200601|,t0,t1|6|
c1|n1|200601|,t0,t1,t2|9|
c1|n1|200601|,t0,t1,t2,t3|12|
c1|n1|200602|,t1|3|
c1|n1|200602|,t1,t2|6|
c1|n1|200603|,t3|3|
c1|n1|200603|,t3,t4|6|
c1|n1|200603|,t3,t4,t5|9|
c1|n2|200601|,t0|3|
c1|n2|200601|,t0,t2|6|
c1|n2|200601|,t0,t2,t3|9|
c1|n2|200601|,t0,t2,t3,t4|12|
c1|n4|200603|,t3|3|
c1|n4|200603|,t3,t4|6|
c1|n4|200603|,t3,t4,t5|9|
c1|n4|200604|,t1|3|
c1|n4|200604|,t1,t2|6|
c1|n4|200604|,t1,t2,t3|9|
c2|n2|200601|,t0|3|
c2|n2|200601|,t0,t2|6|
c2|n2|200601|,t0,t2,t3|9|
c2|n2|200601|,t0,t2,t3,t4|12|
c2|n3|200601|,t5|3|
c2|n3|200602|,t1|3|
c2|n3|200602|,t1,t2|6|
c2|n4|200603|,t3|3|
c3|n4|200603|,t4|3|
c3|n4|200603|,t4,t5|6|
c3|n4|200604|,t1|3|
c3|n4|200604|,t1,t2|6|

А теперь осталось выбрать по каждой группе запись с наибольшей длинной поля FORMMS ( т. е. max(len)) Предлагаю сделать так:

select city,name,period,ltrim(formms,',') result from 

     (select city,name,period,sys_connect_by_path(formm, ',') formms,      
      length(sys_connect_by_path(formm, ','))len from 

          (  
          select city,name, period,goods formm,
         lag(goods) over (partition by city,name,period order by goods ) as form_prev 
         from dbst_tabl 
          )

        start with form_prev is null
        connect by  city=prior city and name=prior name and period=prior period 
            and form_prev=prior formm) ll 

where len=(
  
                   select max(len) from 
                   (select city,name,period,sys_connect_by_path(formm, ',')      
                   formms,length(sys_connect_by_path(formm, ','))len
        from (select city,name, period,goods formm,
        lag(goods) over (partition by city,name,period order by goods ) as form_prev 
        from dbst_tabl )
        start with form_prev is null
        connect by  city=prior city and name=prior name and period=prior period and 
                   form_prev=prior formm) jj where jj.city=ll.city and jj.name=ll.name 
                   and jj.period=ll.period  group by city,name,period)
					  
order by 1,2,3
CITY|NAME|PERIOD|RESULT
c1|n1|200601|t0,t1,t2,t3|
c1|n1|200602|t1,t2|
c1|n1|200603|t3,t4,t5|
c1|n2|200601|t0,t2,t3,t4|
c1|n4|200603|t3,t4,t5|
c1|n4|200604|t1,t2,t3|
c2|n2|200601|t0,t2,t3,t4|
c2|n3|200601|t5|
c2|n3|200602|t1,t2|
c2|n4|200603|t3|
c3|n4|200603|t4,t5|
c3|n4|200604|t1,t2|
Таким образом, мы получили по каждой группе записей перечень значений необходимой колонки. То есть для каждой группы определенную колонку поместили в строку. Немного объясню всю эту кухню.
Построим иерархический запрос:
select level,formm from
(select city,name, period,goods formm,
lag(goods) over (partition by city,name,period order by goods ) as form_prev 
from dbst_tabl)
start with form_prev is null
connect by form_prev=prior formm and city=prior city and name= prior name and period= prior period
LEVEL|FORMM
1|t0|
2|t1|
3|t2|
4|t3|
1|t1|
2|t2|
1|t3|
2|t4|
3|t5|
1|t0|
2|t2|
3|t3|
4|t4|
1|t3|
2|t4|
3|t5|
1|t1|
2|t2|
3|t3|
1|t0|
2|t2|
3|t3|
4|t4|
1|t5|
1|t1|
2|t2|
1|t3|
1|t4|
2|t5|
1|t1|
2|t2|

Таким образом, создали иерархию. А теперь иерархия должна быть отображена в строке. Для этого есть функция SYS_CONNECT_BY_PATH.

SYS_CONNECT_BY_PATH - это функция для иерархических запросов. Возвращает путь значений колонки от корня к узлу (каждое значения колонки разделено указанным разделителем), полученных по условию CONNECT BY. Тип колонки только CHAR, VARCHAR2, NCHAR, or NVARCHAR2. Возвращаемая строка типа VARCHAR2 .

Это делается запросом

(select level,sys_CONNECT_BY_PATH(formm,',') from
(select city,name, period,goods formm,
lag(goods) over (partition by city,name,period order by goods ) as form_prev 
from dbst_tabl)

start with form_prev is null
connect by form_prev=prior formm and city=prior city and name= prior name and period= prior period)
LEVEL|SYS_CONNECT_BY_PATH(FORMM,',')
1|,t0|
2|,t0,t1|
3|,t0,t1,t2|
4|,t0,t1,t2,t3|
1|,t1|
2|,t1,t2|
1|,t3|
2|,t3,t4|
3|,t3,t4,t5|
1|,t0|
2|,t0,t2|
3|,t0,t2,t3|
4|,t0,t2,t3,t4|
1|,t3|
2|,t3,t4|
3|,t3,t4,t5|
1|,t1|
2|,t1,t2|
3|,t1,t2,t3|
1|,t0|
2|,t0,t2|
3|,t0,t2,t3|
4|,t0,t2,t3,t4|
1|,t5|
1|,t1|
2|,t1,t2|
1|,t3|
1|,t4|
2|,t4,t5|
1|,t1|
2|,t1,t2|

Немного теории

Иерархические запросы в Oracle обеспечиваются фразой CONNECT в операторе SELECT:

  • START WITH - задает строку/строки, лежащие в корне иерархии. Эта фраза определяет условие, которому должны соответствовать корневые строки. Условие не должно содержать подзапросов. Если эта фраза не задана, то все строки таблицы являются корневыми.
  • CONNECT BY - задает отношение между родительскими и дочерними строками в иерархии. Отношение задается условием, это может быть любое условие, но какая-то его часть должна содержать оператор PRIOR, относящийся к родительской строке:

Чтобы найти дочерние строки Oracle вычисляет PRIOR-выражение для родительской строки, а другое выражение - для каждой строки таблицы. Строки, для которых это выражение дает истину, являются дочерними. CONNECT BY может содержать и другие условия-фильтры. CONNECT BY не может содержать подзапросов.

Выборки Oracle, использующие иерархические свойства запросов, могут использовать псевдостолбец level. Этот псевдостолбец имеет значение 1 для узла дерева, находящегося в корне, 2 - для узлов, являющихся непосредственными потомками корневого, и т.д.

В Oracle Database 10g для запросов с предложением CONNECT BY появилась целая группа новых функций, таких, как:

  • CONNECT_BY_ROOT – возвращает корень иерархии текущей строки CONNECT BY, эта функция значительно упрощает наш запрос. (Пример см. ниже.);
  • CONNECT_BY_ISLEAF – признак, указывающий, что текущая строка имеет дочерние строки;
  • CONNECT_BY_ISCYCLE – признак, указывающий, что в вашей иерархии текущая строка является началом бесконечного цикла. Например, если A – родитель B, B – родитель C, а C – родитель A, то у вас будет бесконечный цикл. Вы можете использовать этот признак для определения, какая строка или строки ваших данных являются началом бесконечного цикла;
  • NOCYCLE – позволяет в запросе с предложением CONNECT BY распознать, что встретился бесконечный цикл и прекратить выполнение запроса без выдачи ошибки (вместо возврата ошибки зацикливания при выполнении предложения CONNECT BY).

У Вас есть также возможность почитать про аналитические функции (over) в моей статье. Успехов! Если будут замечания, предложения – прошу.

6 комментариев

Прокоментировать

Anonymous
19 марта 2008 г. в 11:11

Спасибо за скрипт.
Но в моём случае он работает только если

select * from (select ltrim(sys_connect_by_path(id, ','),',') as "ID"
    from ( select id,lag(id) over (order by length(id) ) as id_1 from dbst_TAB)
    start with id_1 is null
    connect by id_1 = prior id
    order by 1 desc)
    where rownum=1

Anonymous
20 марта 2008 г. в 18:03

вот решение без иерархий

http://www.sql.ru/faq/faq_topic.aspx?fid=210

dbstalker
21 марта 2008 г. в 11:07

Да, Вы правы. Там,действительно, без иерархий. Но, если Вы обратили внимание, там ограниченное число строк превращается в ограниченное число столбцов. Если Вы уже подняли вопрос транспонирования таблицы, то могу вам посоветовать еще один мой пост Как транспонировать таблицу в ORACLE. Благодарю за внимание к моему блогу!

smarty
4 апреля 2008 г. в 19:38

Решается довольно легко с использованием Data Cartridge и написанием своей агрегирующей (в данном случае склеивающей) функции. Только надо выключить merge, т.к. при паралельном выполнении понятное получим не предсказуемый результат.

dbstalker
7 апреля 2008 г. в 15:14

Было бы всем интересно, если бы Вы показали, как рассматриваемая в посте задача решается с использование Data Cartridge на конкретном примере.

katemur
10 ноября 2009 г. в 11:11

Спасибо

 

Новый комментарий

Я не спамер: введите суму 2+3



 

От авторов блога

О Блоге - прочитай перед началом.

Задать вопрос и получить ответ - уже решено 94 вопросов

Глоссарий - список терминов и сокращений


 
 

Бизнес форум

Последние темы:

Шины бу
26 апреля, 2 ответа
Потрібна порада
25 апреля, 2 ответа
Посоветуйте адвоката
25 апреля, 1 ответа