powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Поиск комбинаций строк в таблице
40 сообщений из 40, показаны все 2 страниц
Поиск комбинаций строк в таблице
    #39457928
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Хочу посоветоваться в решении прикладной задачи. Свой вариант написал, работает точно и быстро, но есть ощущение, что можно сделать "красивее".

Есть абстрактная система CRM, в ней список служб (продуктов) в иерархической форме.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
create table prods as (
  select 1 as prod_id,  null  as parent_id, 'ROOT1'    as prod_name from dual union
  select 2 as prod_id,  1     as parent_id, 'PROD11'   as prod_name from dual union
  select 3 as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 4 as prod_id,  3     as parent_id, 'PROD121'  as prod_name from dual union
  select 5 as prod_id,  3     as parent_id, 'PROD123'  as prod_name from dual union
  select 10 as prod_id, null  as parent_id, 'ROOT1'    as prod_name from dual union
  select 20 as prod_id, 10    as parent_id, 'PROD11'   as prod_name from dual union
  select 30 as prod_id, 10    as parent_id, 'PROD12'   as prod_name from dual union
  select 31 as prod_id, 10    as parent_id, 'PROD13'   as prod_name from dual union
  select 40 as prod_id, 30    as parent_id, 'PROD121'  as prod_name from dual union
  select 41 as prod_id, 30    as parent_id, 'PROD122'  as prod_name from dual union
  select 42 as prod_id, 30    as parent_id, 'PROD123'  as prod_name from dual 
);

-- product tree
select 
  p.prod_id                                 as prod_id, 
  connect_by_root p.prod_id                 as root_id, 
  connect_by_root p.prod_name               as root_prod, 
  rpad(' ', (level-1)*2, ' ')||p.prod_name  as prod_name, 
  sys_connect_by_path(p.prod_name, '|')     as prod_path
from prods p
start with parent_id is null 
connect by prior prod_id = parent_id;        



Есть список комбинаций продуктов.

У меня ключом для комбинаций используются полные пути, так обеспечивается точность иерархии. Мой вариант происходит из excel-таблицы, составленной архитекторами и BA, возможно это повлияло на способ решения задачи. В принципе, список комбинаций может быть в любой форме, главное чтобы сохранились коды (имена) продуктов, их расположение в иерархии отосительно друг друга, была возможность добавлять дополнительные ключи (напр.: параметр продукта из другой таблицы, его состояние, и т.п.).

Максимальное количество продуктов в комбинации ограничено и точно известно. В примере их четыре (далее будет понятно, почему это важно), но в реальности - их всего десять.

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
create table prod_map as
(
  -- map1
  select 'MAP_1'  map_id, 1     map_group, 1    map_depth, '|ROOT1'                 prod_path, 'root'   pos from dual union
  select 'MAP_1'  map_id, 1     map_group, 1    map_depth, '|ROOT1|PROD11'          prod_path, 'prod1'  pos from dual union 
  select 'MAP_1'  map_id, 1     map_group, 1    map_depth, '|ROOT1|PROD12'          prod_path, 'prod2'  pos from dual union 
  -- map2
  select 'MAP_2'  map_id, 1     map_group, 2    map_depth, '|ROOT1'                 prod_path, 'root'   pos from dual union
  select 'MAP_2'  map_id, 1     map_group, 2    map_depth, '|ROOT1|PROD11'          prod_path, 'prod1'  pos from dual union 
  select 'MAP_2'  map_id, 1     map_group, 2    map_depth, '|ROOT1|PROD12'          prod_path, 'prod2'  pos from dual union 
  select 'MAP_2'  map_id, 1     map_group, 2    map_depth, '|ROOT1|PROD13'          prod_path, 'prod3'  pos from dual union 
  -- map3
  select 'MAP_3'  map_id, null  map_group, null map_depth, '|ROOT1'                 prod_path, 'root'   pos from dual union 
  select 'MAP_3'  map_id, null  map_group, null map_depth, '|ROOT1|PROD12'          prod_path, 'prod1'  pos from dual union
  select 'MAP_3'  map_id, null  map_group, null map_depth, '|ROOT1|PROD12|PROD121'  prod_path, 'prod2'  pos from dual union 
  select 'MAP_3'  map_id, null  map_group, null map_depth, '|ROOT1|PROD12|PROD122'  prod_path, 'prod3'  pos from dual  
);



Комбинации могут частично пересекаться - MAP_1 и MAP_2 похожи, в MAP_2 на один продукт больше. В таких случаях при выборе используются параметры map_group и map_depth, выбирается максимальная "глубина" для совпадающей группы.

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

Моё решение - совпадения проверять по строке пути и точному количеству строк.
Делаю вспомогательную табличку:
Код: plsql
1.
2.
3.
create table prod_map_qty as (
  select map_id, count(*) as qty from prod_map group by map_id
);



Главный select. Немного громоздкий, но достаточно быстрый. Найденные комбинации разворачиваются в одну строку пивотом (отдельные столбцы для каждого продукта), и полученный результат фильтруется по map_group и map_depth.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
with
prod_list as (
  select 
    p.prod_id                             as prod_id, 
    connect_by_root p.prod_id             as root_id, 
    connect_by_root p.prod_name           as root_prod, 
    p.prod_name                           as prod_name, 
    sys_connect_by_path(p.prod_name, '|') as prod_path,
    level as lvl
  from prods p
  start with parent_id is null 
  connect by prior prod_id = parent_id                        
)
select * from (
    -- RANK result by map_group, map_depth
    -- get one with maximum map_depth (highest rank)
    SELECT t.*, ROW_NUMBER() OVER (PARTITION BY root_id, map_group ORDER BY map_depth DESC) rank
    FROM 
      ( 
        -- pivot product list from tree form to single row form
        select 
          map_id, map_group, map_depth, 
          unmapped_prod_id, unmapped_prod_path,
          root_prod, root_id, 
          root_pid, prod1_pid, prod2_pid, prod3_pid, prod4_pid
        from 
          (
            -- product list with mapped and unmapped products, tree form, ready for pivoting 
            select 
              v.map_id, v.map_group, v.map_depth, 
              v.unmapped_prod_id, v.unmapped_prod_path,
              v.root_prod, v.root_id, v.prod_id, v.pos
            from 
              (
                -- join product list with mapping tree table
                select
                  vv.map_id,vv.map_group, vv.map_depth, 
                  -- unmapped id
                  case 
                    when vv.prod_id is null then p.prod_id
                    else null
                  end unmapped_prod_id,
                  -- unmapped path
                  case 
                    when vv.prod_id is null then p.prod_path
                    else null
                  end unmapped_prod_path,
                  p.root_prod, p.root_id, p.prod_id, p.prod_path,
                  vv.pos
                from prod_list p
                left join -- return all products, including not matched by mapping table
                  (
                    -- get matched combinations of products 
                    select 
                      vmap.* 
                    from 
                      (
                        -- find matched products combinations by mapping tree table
                        select 
                          m.*, pp.root_id, pp.prod_id,
                          sum(1) over (partition by m.map_id, pp.root_id) as qty  -- qty of matched product paths for ID
                        from prod_map m
                        join prod_list pp on 
                          pp.prod_path = m.prod_path  --  same product path
--                              and ......                  --  additional checks (states, parameters, etc.) 
                      ) vmap
                      -- get fully matched combinations (qty of matched products == qty of products by mapping table)
                      join prod_map_qty q on q.map_id = vmap.map_id and q.qty = vmap.qty
                  ) vv on vv.prod_id = p.prod_id
              ) v
          ) 
          -- PIVOT result to single row for each mapped_id
          PIVOT ( max(prod_id) AS pid FOR (pos) IN 
            (
             'root' as root,
             'prod1' as prod1, 
             'prod2' as prod2, 
             'prod3' as prod3, 
             'prod4' as prod4
            )
          ) -- end PIVOT 
      ) t
  ) res
  WHERE ( res.map_group is null ) 
        OR 
        -- for multiple map_group - get one with highest priority (highest rank)
        ( res.map_group is not null and res.rank = 1 )
order by root_id, map_id;



Если на вход подать сразу несколько миллионов строк, то он будет или тормозить или переполнит temp_segment. Как самостоятельный селект он используется только для проверки единичных вариантов. В боевом исполнении он встроен в parallel pipelined функцию, на вход получает от одного до сотни клиентов. С таким количеством справляется влёт.

Но я продолжаю ломать голову - нет ли другого, более элегантного решения поиска заданных комбинаций в таблице? Может кто-то решал что-то подобное?
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39459433
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Никто? ;)
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39459524
Фотография env
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarov,

multiset?
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39459628
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovЗадача - найти в списке продуктов в рамках одного дерева точные комбинации в соответствии со списком, а также продукты, для которых комбинаций не нашлись.
Код: plsql
1.
select * from product_tree full join prod_map using (prod_path)


?
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39459757
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshop
Код: plsql
1.
select * from product_tree full join prod_map using (prod_path)


?

Так получается список вообще всего, полного совпадения и неполного. Нужно только полное совпадение комбинации, например для root_id = 1 не подходит MAP_2, так как отсуствует один из четырёх продуктов этой комбинации - '|ROOT1|PROD13'.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39459956
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarov,

Цель найти root_id для каждого map, при этом root_id считается подходящим если его дети содержат все пути для конкретного map?

Если для одного map подходит более одного root_id - какой берем?

rpovarovУ меня ключом для комбинаций используются полные пути, так обеспечивается точность иерархии.Пути (составленные из prod_name) в твоей иерархии неуникальные, о каком ключе может быть речь?

rpovarovПрикладное применение - миграция из одной системы в другую, комбинации продуктов из старой системы превращаются в другие комбинации в новой, на основании таблиц, составленных бизнесом и архитекторами.Так меняется иерархия или связи родитель-потомок остаются как есть, а меняются атрибуты сущности (пусть даже все атрибуты, включая имя)?
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39459998
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshopЦель найти root_id для каждого map, при этом root_id считается подходящим если его дети содержат все пути для конкретного map?
Если для одного map подходит более одного root_id - какой берем?

В обратном направлении - цель найти все map_id для дерева под одним root_id.

В системе одна бизнес-служба (как её видит бизнес-аналитик) состоит из нескольких "технических" продуктов. К примеру, комбинация MAP_1 из трёх продуктов (ROOT1, PROD11 и PROD12) может быть интернет-тарифом, пусть будет INTERNET_STANDARD. А то же самое, но с ещё одним дополнительным PROD13, даёт комбинацию MAP_2, которая для бизнес-аналитика - интернет с телевидением, пусть будет INTERNET_IPTV.

Бизнес придумал обновить CRM и всё это перенести в новую систему. Там всё по другому, миграции один к одному не получится. Поэтому миграция будет проходить на уровне бизнес-служб, которые в новой системе уже сами развернутся как надо, по своим правилам.

И вот бизнес-аналитики с другими специалистами по продуктам составляют огромную Excel таблицу, в которой перечислены все существующие в старой системе комбинации продуктов и как они складываются в службы. Из такой таблицы после обработки в результате получится prod_map.

Задача - получить список MAP_1, MAP_2, ... MAP_N для каждого отдельного подписчика (будем считать, что один клиент имеет всего одно дерево продуктов, тогда root_id - это ключ, который однозначно идентифицирует клиента). Должно быть полное совпадение списка продуктов из конфигурации MAP_x, например, если есть |ROOT1 и |ROOT1|PROD12, но нет |ROOT1|PROD11, то нет и совпадения.

Продукты, для которых не нашлась комбинация, надо найти и показать бизнес-аналитикам, чтобы они решили, что с ними делать. Как вариант, чтобы выпустили новую обновлённую версию prod_map.

Т.е. на входе у нас prods, а на выходе получаем список MAP_x и информацию о тех продуктах, для которых MAP_x не нашлись. Не должно остаться ни одного забытого продукта.

dbms_photoshopПути (составленные из prod_name) в твоей иерархии неуникальные, о каком ключе может быть речь?

Ключ, наверное, не совсем правильное слово. Путь - это идентификатор, показывающий однозначное иерархическое подчинение продукта. Потому что один и тот же продукт с кодом PROD11 может находится как под ROOT1, и тогда это будет одна служба, так и под ROOT2 или ROOT3, и тогда это будет совсем другая служба и другой тариф. А вот комбинация путей (набор строчек под единым MAP_x) должна быть уникальная.

dbms_photoshopТак меняется иерархия или связи родитель-потомок остаются как есть, а меняются атрибуты сущности (пусть даже все атрибуты, включая имя)?
Меняется вообще всё. Из старой системы надо вытащить данные и поднять их на бизнес-уровень, как список служб (т.е. map_id). Который на следующем шаге превратится в исходные данные для создания этих служб в новой системе. Или в репорт качества данных, или ещё во что-нибудь...
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39460073
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovВ обратном направлении - цель найти все map_id для дерева под одним root_id.+ отсутствующие продукты из маппинга.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
select root_id,
       map_id,
       listagg(nvl2(map_prod_path, null, prod_id), ', ') within group(order by prod_id) missing_prods
  from (select pt.*,
               pm.*,
               count(pm.map_prod_path) over(partition by pt.root_id, pm.map_id) cnt_root_map
          from product_tree pt
          left join (select map_id,
                           map_group,
                           map_depth,
                           prod_path map_prod_path,
                           pos,
                           count(*) over(partition by map_id) cnt
                      from prod_map) pm partition by(pm.map_id, pm.cnt)
            on pt.prod_path = pm.map_prod_path) t0
 where cnt_root_map = cnt
 group by root_id, map_id
 order by root_id, map_id;

   ROOT_ID MAP_I MISSING_PRODS
---------- ----- ---------------
         1 MAP_1 4, 5
        10 MAP_1 31, 40, 41, 42
        10 MAP_2 40, 41, 42
        10 MAP_3 20, 31, 42
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39460074
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarov,

Учись формулировать проще и без лишней шелухи.
В твоем сообщении 8 раз встречается слово "бизнес". :)
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39460076
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshoprpovarov,

Учись формулировать проще и без лишней шелухи.
В твоем сообщении 8 раз встречается слово "бизнес". :)
Это локальный сленг, мы так называем внешнюю стихийную силу, раздающую нам указания ;)
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39460211
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshop+ отсутствующие продукты из маппинга.
Код: plsql
1.
2.
3.
4.
5.
6.
   ROOT_ID MAP_I MISSING_PRODS
---------- ----- ---------------
         1 MAP_1 4, 5
        10 MAP_1 31, 40, 41, 42
        10 MAP_2 40, 41, 42
        10 MAP_3 20, 31, 42



В этом варианте отсутствующие продукты относительно одной конкретной комбинации, а нужно относительно всех найденных. В варианте root_id = 10 бесхозным остался только продукт 42, потому что 31 используется в MAP_2, 41 в MAP_3 и т.д.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39460296
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovdbms_photoshop+ отсутствующие продукты из маппинга.
Код: plsql
1.
2.
3.
4.
5.
6.
   ROOT_ID MAP_I MISSING_PRODS
---------- ----- ---------------
         1 MAP_1 4, 5
        10 MAP_1 31, 40, 41, 42
        10 MAP_2 40, 41, 42
        10 MAP_3 20, 31, 42



В этом варианте отсутствующие продукты относительно одной конкретной комбинации, а нужно относительно всех найденных. В варианте root_id = 10 бесхозным остался только продукт 42, потому что 31 используется в MAP_2, 41 в MAP_3 и т.д.
Объяснять это явно не твой конек.
Если бы в голове была ясная картина, то и объяснить было бы просто и запрос написать.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
select root_id, map_id, prod_id
  from (select root_id,
               decode(cnt, cnt_root_map, map_id) map_id,
               prod_id,
               rank() over(partition by root_id, prod_id order by decode(cnt, cnt_root_map, 1)) rnk
          from (select pt.*,
                       pm.*,
                       count(pm.map_prod_path) over(partition by pt.root_id, pm.map_id) cnt_root_map
                  from product_tree pt
                  left join (select map_id,
                                   map_group,
                                   map_depth,
                                   prod_path map_prod_path,
                                   pos,
                                   count(*) over(partition by map_id) cnt
                              from prod_map) pm
                    on pt.prod_path = pm.map_prod_path) t0)
 where rnk = 1
 order by root_id, map_id, prod_id;

   ROOT_ID MAP_I    PROD_ID
---------- ----- ----------
         1 MAP_1          1
         1 MAP_1          2
         1 MAP_1          3
         1                4
         1                5
        10 MAP_1         10
        10 MAP_1         20
        10 MAP_1         30
        10 MAP_2         10
        10 MAP_2         20
        10 MAP_2         30
        10 MAP_2         31
        10 MAP_3         10
        10 MAP_3         30
        10 MAP_3         40
        10 MAP_3         41
        10               42

17 rows selected.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39460475
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshopОбъяснять это явно не твой конек.
Если бы в голове была ясная картина, то и объяснить было бы просто и запрос написать.

Может и так. Мне казалось, что в самом первом сообщении написал понятно, плюс ещё исходники.
Комбинации могут частично пересекаться - MAP_1 и MAP_2 похожи, в MAP_2 на один продукт больше. В таких случаях при выборе используются параметры map_group и map_depth, выбирается максимальная "глубина" для совпадающей группы.

Задача - найти в списке продуктов в рамках одного дерева точные комбинации в соответствии со списком, а также продукты, для которых комбинаций не нашлись.

В любом случае твой вариант интереснее и чище. Спасибо! Вот он же с пивотом и фильтрацией по map_depth.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
select 
  root_id, map_id,
  unmapped_id, unmapped_path,
  root_pid, prod1_pid, prod2_pid, prod3_pid, prod4_pid
from 
  (select 
    root_id, map_id, unmapped_id, unmapped_path, pos, prod_id,
    rank() over (partition by root_id, map_group order by map_depth desc) group_rank
   from (select root_id,
               decode(cnt, cnt_root_map, map_id) map_id,
               decode(cnt, cnt_root_map, null, prod_id) unmapped_id,
               decode(cnt, cnt_root_map, null, prod_path) unmapped_path,
               map_group,
               map_depth,
               prod_id,
               decode(cnt, cnt_root_map, pos, null) pos,
               rank() over(partition by root_id, prod_id  order by decode(cnt, cnt_root_map, 1)) map_rank
          from (select pt.*,
                       pm.*,
                       count(pm.map_prod_path) over(partition by pt.root_id, pm.map_id) cnt_root_map
                  from product_tree pt
                  left join (select map_id,
                                   map_group,
                                   map_depth,
                                   prod_path map_prod_path,
                                   pos,
                                   count(*) over(partition by map_id) cnt
                              from prod_map) pm
                    on pt.prod_path = pm.map_prod_path)) where map_rank = 1)
          -- PIVOT result to single row for each map_id
          PIVOT ( max(prod_id) AS pid FOR (pos) IN 
            (
             'root' as root,
             'prod1' as prod1, 
             'prod2' as prod2, 
             'prod3' as prod3, 
             'prod4' as prod4
            )
          ) -- end PIVOT 
 where group_rank = 1
 order by root_id, map_id;
 




Я его ещё проверю на боевой конфигурации, потом поделюсь впечатлениями.

И за "outer join partition by" отдельное спасибо, этот вариант джойна вообще прошёл мимо меня, пойду читать документацию.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39461778
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Усложняем. Вылез вариант, когда может быть параллельно несколько одинаковых ветвей под одним корнем.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
create table prods as (
  select 1 as prod_id,  null  as parent_id, 'ROOT1'    as prod_name from dual union
  select 2 as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 3 as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 4 as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 5 as prod_id,  2     as parent_id, 'PROD121'  as prod_name from dual union
  select 6 as prod_id,  3     as parent_id, 'PROD121'  as prod_name from dual union
  select 7 as prod_id,  4     as parent_id, 'PROD121'  as prod_name from dual union
  select 8 as prod_id,  1     as parent_id, 'PROD13'   as prod_name from dual union
  select 9 as prod_id,  8     as parent_id, 'PROD121'  as prod_name from dual
);

create table product_tree as
select 
  prod_id,
  parent_id,
  connect_by_root p.prod_id                 as root_id, 
  connect_by_root p.prod_name               as root_prod, 
  p.prod_name                               as prod_name, 
  rpad(' ', (level-1)*2, ' ')||p.prod_name  as prod_name_pad, 
  sys_connect_by_path(p.prod_name, '|')     as prod_path
from prods p
start with parent_id is null 
connect by prior prod_id = parent_id;        

PROD_NAME_PAD	PROD_PATH
=============   =========
ROOT1	        |ROOT1
  PROD12	|ROOT1|PROD12
    PROD121	|ROOT1|PROD12|PROD121
  PROD12	|ROOT1|PROD12
    PROD121	|ROOT1|PROD12|PROD121
  PROD12	|ROOT1|PROD12
    PROD121	|ROOT1|PROD12|PROD121
  PROD13	|ROOT1|PROD13
    PROD121	|ROOT1|PROD13|PROD121

create table prod_map as
(
  select 'MAP_4'  map_id, 1  map_group, 1 map_depth, '|ROOT1'                 prod_path, 'root'   pos from dual union 
  select 'MAP_4'  map_id, 1  map_group, 1 map_depth, '|ROOT1|PROD12'          prod_path, 'prod1'  pos from dual union
  select 'MAP_4'  map_id, 1  map_group, 1 map_depth, '|ROOT1|PROD12|PROD121'  prod_path, 'prod2'  pos from dual
);



На выходе, соответственно, должны получиться три ветви с map_id = MAP4 (и продукты 8 и 9 как unmapped).

Уже всю голову сломал, как от себя отделить эти ветви. Если получится размножить корневой продукт отдельно к каждой ветви, повесить на всю ветвь какой-нибудь id, то потом этот id можно прописать в "... over(partition by..." рядом с root_id, и всё должно сработать.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39461818
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovУже всю голову сломал, как от себя отделить эти ветви. Если получится размножить корневой продукт отдельно к каждой ветви, повесить на всю ветвь какой-нибудь id, то потом этот id можно прописать в "... over(partition by..." рядом с root_id, и всё должно сработать.Ну ты был близок. А в чем проблема размножить?
Либо self join по like (как в примере ниже) либо строить дерево сначала от корня, а потом к корню.

Идея та же, только использовано размноженное дерево вместо оригинального (super_tree instead of product_tree) и добавлен leaf_id в логику.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
with prods as (
  select 1 as prod_id,  null  as parent_id, 'ROOT1'    as prod_name from dual union
  select 2 as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 3 as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 4 as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 5 as prod_id,  2     as parent_id, 'PROD121'  as prod_name from dual union
  select 6 as prod_id,  3     as parent_id, 'PROD121'  as prod_name from dual union
  select 7 as prod_id,  4     as parent_id, 'PROD121'  as prod_name from dual union
  select 8 as prod_id,  1     as parent_id, 'PROD13'   as prod_name from dual union
  select 9 as prod_id,  8     as parent_id, 'PROD121'  as prod_name from dual
),
product_tree as (
select
  prod_id,
  parent_id,
  connect_by_root p.prod_id                 as root_id,
  connect_by_root p.prod_name               as root_prod,
  p.prod_name                               as prod_name,
  rpad(' ', (level-1)*2, ' ')||p.prod_name  as prod_name_pad,
  sys_connect_by_path(p.prod_name, '|')     as prod_path,
  --
  sys_connect_by_path(p.prod_id, '|')       as unique_path,
  connect_by_isleaf                         as isleaf
from prods p
start with parent_id is null
connect by prior prod_id = parent_id),
super_tree as (
select pt1.prod_id leaf_id, pt2.*
  from product_tree pt1
  left join product_tree pt2
    on pt1.unique_path like pt2.unique_path || '%'
 where pt1.isleaf = 1),
prod_map as
(
  select 'MAP_4'  map_id, 1  map_group, 1 map_depth, '|ROOT1'                 prod_path, 'root'   pos from dual union
  select 'MAP_4'  map_id, 1  map_group, 1 map_depth, '|ROOT1|PROD12'          prod_path, 'prod1'  pos from dual union
  select 'MAP_4'  map_id, 1  map_group, 1 map_depth, '|ROOT1|PROD12|PROD121'  prod_path, 'prod2'  pos from dual
)
select root_id, leaf_id, map_id, prod_id, prod_path
  from (select root_id,
               leaf_id,
               decode(cnt, cnt_root_map, map_id) map_id,
               prod_id,
               prod_path,
               rank() over(partition by root_id, prod_id order by decode(cnt, cnt_root_map, 1)) rnk
          from (select pt.*,
                       pm.*,
                       count(pm.map_prod_path) over(partition by pt.root_id, pt.leaf_id, pm.map_id) cnt_root_map
                  from super_tree pt
                  left join (select map_id,
                                   map_group,
                                   map_depth,
                                   prod_path map_prod_path,
                                   pos,
                                   count(*) over(partition by map_id) cnt
                              from prod_map) pm
                    on pt.prod_path = pm.map_prod_path) t0)
 where rnk = 1
 order by root_id, leaf_id, map_id, prod_id;

   ROOT_ID    LEAF_ID MAP_I    PROD_ID PROD_PATH
---------- ---------- ----- ---------- ------------------------------
         1          5 MAP_4          1 |ROOT1
         1          5 MAP_4          2 |ROOT1|PROD12
         1          5 MAP_4          5 |ROOT1|PROD12|PROD121
         1          6 MAP_4          1 |ROOT1
         1          6 MAP_4          3 |ROOT1|PROD12
         1          6 MAP_4          6 |ROOT1|PROD12|PROD121
         1          7 MAP_4          1 |ROOT1
         1          7 MAP_4          4 |ROOT1|PROD12
         1          7 MAP_4          7 |ROOT1|PROD12|PROD121
         1          9                8 |ROOT1|PROD13
         1          9                9 |ROOT1|PROD13|PROD121

11 rows selected.

...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39461823
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshopНу ты был близок. А в чем проблема размножить?
Либо self join по like (как в примере ниже) либо строить дерево сначала от корня, а потом к корню.


Проблема, как всегда, в ДНК :)
Я раньше подобные задачи решал нахрапом черед PL/SQL, но практика показала, что чем больше запихнуть в SQL (в разумных пределах), тем лучше и быстрее в результате всё работает. Но мозги перестраивать силком надо...

Спасибо за пример! Завтра погоняю на работе, может ещё что найду интересного.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39461826
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovdbms_photoshopНу ты был близок. А в чем проблема размножить?
Либо self join по like (как в примере ниже) либо строить дерево сначала от корня, а потом к корню.


Проблема, как всегда, в ДНК :)
Я раньше подобные задачи решал нахрапом черед PL/SQL, но практика показала, что чем больше запихнуть в SQL (в разумных пределах), тем лучше и быстрее в результате всё работает. Но мозги перестраивать силком надо...

Спасибо за пример! Завтра погоняю на работе, может ещё что найду интересного.Это хорошо, что ты перестал заниматься глупостями. Я закончил свое исследование разумных пределов SQL vs PL/SQL, надеюсь до конца недели выложить. :)
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39461829
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshopЭто хорошо, что ты перестал заниматься глупостями. Я закончил свое исследование разумных пределов SQL vs PL/SQL, надеюсь до конца недели выложить. :)
Была одна задачка, которая чистым SQL не решалась совсем или решалась с огромным геморроем и ограничениями. Тоже обход дерева, но нет прямой иерархии, она строится на ходу в зависимости от того, что нашлось в каждой ветке. Конфигурация была написана для Java-кода в бэкенде, и он там успешно работает. Потребовалось его повторить, но прямо над базой данных. Обход дерева рекурсией в PL/SQL, но там, где ситуация позволяет, скармливаю таблицы в SQL. В результате довольно быстро работает (если распараллелить), и побочным эффектом вывод в логи или на экран всего построенного дерева с комментариями :)
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39461835
Фотография Elic
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovБыла одна задачка, которая чистым SQL не решалась совсем или решалась с огромным геморроем и ограничениями. Тоже обход дерева, но нет прямой иерархии, она строится на ходу в зависимости от того, что нашлось в каждой ветке. Конфигурация была написана для Java-кода в бэкенде, и он там успешно работает. Потребовалось его повторить, но прямо над базой данных. Обход дерева рекурсией в PL/SQL, но там, где ситуация позволяет, скармливаю таблицы в SQL. В результате довольно быстро работает (если распараллелить), и побочным эффектом вывод в логи или на экран всего построенного дерева с комментариями :)Описывая свой "любимый говнокод" своими же ощущениями наивно полагать встретить понимание.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39461862
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovнет прямой иерархии, она строится на ходу в зависимости от того, что нашлось в каждой веткеЭто наводит на мысль, что на SQL было бы уместнее решать с помощью rec with чем connect by.
А я имею в виду, что есть случаи, когда PL/SQL предпочтительнее базового SQL без ухищрений.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39462384
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshopИдея та же, только использовано размноженное дерево вместо оригинального (super_tree instead of product_tree) и добавлен leaf_id в логику.

Если в дереве и prod_map попадается комбинация, где несколько последних в цепочке продуктов на одном уровне

Код: plaintext
1.
2.
3.
|ROOT2
|ROOT2|PROD12
|ROOT2|PROD12|PROD121
|ROOT2|PROD12|PROD122

но при этом нет параллельных ветвей, то получается интересно :)
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
ROOT_ID	LEAF_ID	MAP_ID	PROD_ID	PROD_PATH
100	120		100	|ROOT2
100	120		110	|ROOT2|PROD12
100	120		120	|ROOT2|PROD12|PROD121
100	130		100	|ROOT2
100	130		110	|ROOT2|PROD12
100	130		130	|ROOT2|PROD12|PROD122

Два "хвоста" образовали каждый свою ветку по leaf_id. При этом, если присутствуют параллельные ветки, то всё в порядке
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
ROOT_ID	LEAF_ID	MAP_ID	PROD_ID	PROD_PATH
1	60	MAP_4	1	|ROOT1
1	60	MAP_4	3	|ROOT1|PROD12
1	60	MAP_4	6	|ROOT1|PROD12|PROD121
1	60	MAP_4	60	|ROOT1|PROD12|PROD122
1	70	MAP_4	1	|ROOT1
1	70	MAP_4	4	|ROOT1|PROD12
1	70	MAP_4	7	|ROOT1|PROD12|PROD121
1	70	MAP_4	70	|ROOT1|PROD12|PROD122

Немного поправил product_tree и super_tree, размножаю только множественные случаи
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
product_tree as (
select
  prod_id,
<...skip...>
  connect_by_isleaf                         as isleaf,
  --
  count(*) over (partition by sys_connect_by_path(p.prod_name, '|')) as qty
from prods p
start with parent_id is null
connect by prior prod_id = parent_id),
super_tree as (
  select -1 leaf_id, pt1.*
    from product_tree pt1
   where pt1.qty = 1 
  union all  
  select pt1.prod_id leaf_id, pt2.*
    from product_tree pt1
    left join product_tree pt2
      on pt1.unique_path like pt2.unique_path || '%' 
   where pt1.qty > 1 and pt1.isleaf = 1 
),



Результат
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
ROOT_ID	LEAF_ID	MAP_ID	PROD_ID	PROD_PATH
1	60	MAP_4	1	|ROOT1
1	60	MAP_4	3	|ROOT1|PROD12
1	60	MAP_4	6	|ROOT1|PROD12|PROD121
1	60	MAP_4	60	|ROOT1|PROD12|PROD122
1	70	MAP_4	1	|ROOT1
1	70	MAP_4	4	|ROOT1|PROD12
1	70	MAP_4	7	|ROOT1|PROD12|PROD121
1	70	MAP_4	70	|ROOT1|PROD12|PROD122
100	-1	MAP_5	100	|ROOT2
100	-1	MAP_5	110	|ROOT2|PROD12
100	-1	MAP_5	120	|ROOT2|PROD12|PROD121
100	-1	MAP_5	130	|ROOT2|PROD12|PROD122

Весь код
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
 with prods as (
  select 1  as prod_id,  null  as parent_id, 'ROOT1'    as prod_name from dual union
  select 2  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 3  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 4  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 5  as prod_id,  2     as parent_id, 'PROD121'  as prod_name from dual union
  select 6  as prod_id,  3     as parent_id, 'PROD121'  as prod_name from dual union
  select 7  as prod_id,  4     as parent_id, 'PROD121'  as prod_name from dual union
  --
  select 50 as prod_id,  2     as parent_id, 'PROD122'  as prod_name from dual union
  select 60 as prod_id,  3     as parent_id, 'PROD122'  as prod_name from dual union
  select 70 as prod_id,  4     as parent_id, 'PROD122'  as prod_name from dual union
  --
  select 8  as prod_id,  1     as parent_id, 'PROD13'   as prod_name from dual union
  select 9  as prod_id,  8     as parent_id, 'PROD121'  as prod_name from dual union
  --
  select 100 as prod_id,  null   as parent_id, 'ROOT2'    as prod_name from dual union
  select 110 as prod_id,  100    as parent_id, 'PROD12'   as prod_name from dual union
  select 120 as prod_id,  110    as parent_id, 'PROD121'  as prod_name from dual union
  select 130 as prod_id,  110    as parent_id, 'PROD122'  as prod_name from dual 
),
product_tree as (
select
  prod_id,
  parent_id,
  connect_by_root p.prod_id                 as root_id,
  connect_by_root p.prod_name               as root_prod,
  p.prod_name                               as prod_name,
  rpad(' ', (level-1)*2, ' ')||p.prod_name  as prod_name_pad,
  sys_connect_by_path(p.prod_name, '|')     as prod_path,
  --
  sys_connect_by_path(p.prod_id, '|')       as unique_path,
  connect_by_isleaf                         as isleaf,
  --
  count(*) over (partition by sys_connect_by_path(p.prod_name, '|')) as qty
from prods p
start with parent_id is null
connect by prior prod_id = parent_id),
super_tree as (
  select -1 leaf_id, pt1.*
    from product_tree pt1
   where pt1.qty = 1 
  union all  
  select pt1.prod_id leaf_id, pt2.*
    from product_tree pt1
    left join product_tree pt2
      on pt1.unique_path like pt2.unique_path || '%' 
   where pt1.qty > 1 and pt1.isleaf = 1 
),
prod_map as
(
  select 'MAP_4'  map_id, 1  map_group, 2 map_depth, '|ROOT1'                 prod_path, 'root'   pos from dual union
  select 'MAP_4'  map_id, 1  map_group, 2 map_depth, '|ROOT1|PROD12'          prod_path, 'prod1'  pos from dual union
  select 'MAP_4'  map_id, 1  map_group, 2 map_depth, '|ROOT1|PROD12|PROD121'  prod_path, 'prod2'  pos from dual union 
  select 'MAP_4'  map_id, 1  map_group, 2 map_depth, '|ROOT1|PROD12|PROD122'  prod_path, 'prod3'  pos from dual union
  --
  select 'MAP_5'  map_id, 1  map_group, 2 map_depth, '|ROOT2'                 prod_path, 'root'   pos from dual union
  select 'MAP_5'  map_id, 1  map_group, 2 map_depth, '|ROOT2|PROD12'          prod_path, 'prod1'  pos from dual union
  select 'MAP_5'  map_id, 1  map_group, 2 map_depth, '|ROOT2|PROD12|PROD121'  prod_path, 'prod2'  pos from dual union 
  select 'MAP_5'  map_id, 1  map_group, 2 map_depth, '|ROOT2|PROD12|PROD122'  prod_path, 'prod3'  pos from dual
)
select root_id, leaf_id, map_id, prod_id, prod_path
  from (select root_id,
               leaf_id,
               decode(cnt, cnt_root_map, map_id) map_id,
               prod_id,
               prod_path,
               rank() over(partition by root_id, prod_id order by decode(cnt, cnt_root_map, 1)) rnk
          from (select pt.*,
                       pm.*,
                       count(pm.map_prod_path) over(partition by pt.root_id, pt.leaf_id, pm.map_id) cnt_root_map
                  from super_tree pt
                  left join (select map_id,
                                   map_group,
                                   map_depth,
                                   prod_path map_prod_path,
                                   pos,
                                   count(*) over(partition by map_id) cnt
                              from prod_map) pm
                    on pt.prod_path = pm.map_prod_path) t0)
 where rnk = 1
 order by root_id, leaf_id, map_id, prod_id;

...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39462412
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovДва "хвоста" образовали каждый свою ветку по leaf_id. При этом, если присутствуют параллельные ветки, то всё в порядке
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
ROOT_ID	LEAF_ID	MAP_ID	PROD_ID	PROD_PATH
1	60	MAP_4	1	|ROOT1
1	60	MAP_4	3	|ROOT1|PROD12
1	60	MAP_4	6	|ROOT1|PROD12|PROD121
1	60	MAP_4	60	|ROOT1|PROD12|PROD122
1	70	MAP_4	1	|ROOT1
1	70	MAP_4	4	|ROOT1|PROD12
1	70	MAP_4	7	|ROOT1|PROD12|PROD121
1	70	MAP_4	70	|ROOT1|PROD12|PROD122

Откуда тут взялось 8 строк вместо шести?? Выше в примере два листа грубины три. 3*2=6.

rpovarovНемного поправил product_tree и super_tree, размножаю только множественные случаиКакое отношение этот фикс имеет к реальной жизни?
Или, говоря твоим сленгом, какое бизнес правило говорит, что надо "размножать только множественные"?
Больше похоже либо на костыль либо для кривых данных либо от недопонимания принципа работы.

Это как некоторые разработчики которые не хотят вникать до конца почему запрос возвращает дубли просто засовывают в него distinct и не парятся.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39462487
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshop,

Этих строчек не было в исходном примере. Я проверял скрипт на боевых таблицах, и нашёл комбинацию, при которой вылезает косяк. Оформил это в виде нового примера и запостил вместе со своим костылём.

То, что в старой системе было в одном экземпляре, в новой тоже будет в одном. Например, тариф. Если было несколько одинаковых веток, то и в новой тоже будет столько же. Например, несколько сим-карт к одному тарифу.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39462510
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovdbms_photoshop,

Этих строчек не было в исходном примере. Я проверял скрипт на боевых таблицах, и нашёл комбинацию, при которой вылезает косяк. Оформил это в виде нового примера и запостил вместе со своим костылём.

То, что в старой системе было в одном экземпляре, в новой тоже будет в одном. Например, тариф. Если было несколько одинаковых веток, то и в новой тоже будет столько же. Например, несколько сим-карт к одному тарифу.Я не говорил про исходный пример. Я ссылался на последнее сообщение.
Еще раз, откуда у тебя взялось 8 вместо 6.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
with prods as (
  select 100 as prod_id,  null  as parent_id, 'ROOT1'    as prod_name from dual union
  select 110 as prod_id,  100   as parent_id, 'PROD12'   as prod_name from dual union
  select 120 as prod_id,  110   as parent_id, 'PROD121'  as prod_name from dual union
  select 130 as prod_id,  110   as parent_id, 'PROD122'  as prod_name from dual
),
product_tree as (
select
  prod_id,
  parent_id,
  connect_by_root p.prod_id                 as root_id,
  connect_by_root p.prod_name               as root_prod,
  p.prod_name                               as prod_name,
  rpad(' ', (level-1)*2, ' ')||p.prod_name  as prod_name_pad,
  sys_connect_by_path(p.prod_name, '|')     as prod_path,
  --
  sys_connect_by_path(p.prod_id, '|')       as unique_path,
  connect_by_isleaf                         as isleaf
from prods p
start with parent_id is null
connect by prior prod_id = parent_id),
super_tree as (
select pt1.prod_id leaf_id, pt2.*
  from product_tree pt1
  left join product_tree pt2
    on pt1.unique_path like pt2.unique_path || '%'
 where pt1.isleaf = 1)
select * from super_tree;



Касательно костылей, нормальный подход как раз отличается от костыля тем,
что данные приводятся в нужную форму до применения логики.
А говнокод - это когда начинает что-то вылазить неожиданное и не учтенное во входных данных,
а разработчик пытается от этого избавиться в процессе.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39462529
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshop,

В сообщении 20521106 , где начали решать проблему одинаковых ветвей, пример был упрощённый, из трёх продуктов. Моя ошибка. Если бы взял конфигурацию для четырёх продуктов из самого первого сообщения - было бы правильнее. "Хорошая мысля приходит опосля..."
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39462573
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarov,

Было бы намного понятнее, если бы ты в моем последнем запросе просто подставил данные на которых он возвращает не то, что ты ожидаешь.
Мне не совсем понятно что не так, хотя если твое решение тебя вполне устраивает, на этом можно и зарешить.

По числу отвечающих в этой ветке ты можешь сделать определенные выводы про умение просто подавать информацию и доступно объяснять.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39462937
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshopБыло бы намного понятнее, если бы ты в моем последнем запросе просто подставил данные на которых он возвращает не то, что ты ожидаешь.
Мне не совсем понятно что не так, хотя если твое решение тебя вполне устраивает, на этом можно и зарешить.


Прошу прощения, что не сразу отвечаю. Не успеваю иногда. Пытался разобраться, почему у меня на, казалось бы, одинаковых данных выходят разные результаты. Подготовлю входные данные, которые должны будут покрыть все спорные варианты, и вернусь.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39471411
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Возвращаюсь с новым решением. Основной проблемой было то, что при нескольких экземплярах одного и того же продукта нужно отделить эти экземпляры уникальным ключом так, чтобы в эту выделенную ветку попали также корневой продукт и остальные одиночные продукты (это важно для дальнейшего "опознания" комбинаций). Решил разделить super_tree из предыдущих попыток на два отдельных шага. В результате получается так:
1. Первый проход по дереву, строим product_path и unique_path, считаем количество повторений product_path.
2. Проход по результату из п.1 рекурсивным селектом, ищем повторяющиеся ветки. Как только наткнулись - вешаем на ветку идентификатор branch_id, и обозначаем им все остальные продукты ниже в этой ветке. Теоретически ниже может быть ещё одно разветвление с повтором (в реальных данных такого не встречается), оно игнорируется.
3. Для всех повторяющихся веток размножаем корневые и неповторяющиеся продукты.

Наверняка можно совместить шаг 1 и 2 в один, но я пока не придумал как.


Исходная таблица
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
with prods as (
  select 1  as prod_id,  null  as parent_id, 'ROOT1'    as prod_name from dual union
  select 2  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 3  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 4  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 5  as prod_id,  2     as parent_id, 'PROD121'  as prod_name from dual union
  select 6  as prod_id,  3     as parent_id, 'PROD121'  as prod_name from dual union
  select 7  as prod_id,  4     as parent_id, 'PROD121'  as prod_name from dual union
  select 50 as prod_id,  2     as parent_id, 'PROD122'  as prod_name from dual union
  select 60 as prod_id,  3     as parent_id, 'PROD122'  as prod_name from dual union
  select 70 as prod_id,  4     as parent_id, 'PROD123'  as prod_name from dual union
  select 8  as prod_id,  1     as parent_id, 'PROD2'    as prod_name from dual union
  select 9  as prod_id,  8     as parent_id, 'PROD22'   as prod_name from dual union
  select 10 as prod_id,  null  as parent_id, 'ROOT2'    as prod_name from dual union
  select 11 as prod_id,  10    as parent_id, 'PROD22'   as prod_name from dual 
)  
  select 
    prod_id, parent_id, 
    rpad(' ', (level-1)*2, ' ')||p.prod_name prod_name, 
    sys_connect_by_path(p.prod_name, '|') prod_path
  from prods p start with parent_id is null
  connect by prior prod_id = parent_id
  order siblings by prod_id;



После применения super_tree
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
with prods as (
  select 1  as prod_id,  null  as parent_id, 'ROOT1'    as prod_name from dual union
  select 2  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 3  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 4  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 5  as prod_id,  2     as parent_id, 'PROD121'  as prod_name from dual union
  select 6  as prod_id,  3     as parent_id, 'PROD121'  as prod_name from dual union
  select 7  as prod_id,  4     as parent_id, 'PROD121'  as prod_name from dual union
  select 50 as prod_id,  2     as parent_id, 'PROD122'  as prod_name from dual union
  select 60 as prod_id,  3     as parent_id, 'PROD122'  as prod_name from dual union
  select 70 as prod_id,  4     as parent_id, 'PROD123'  as prod_name from dual union
  select 8  as prod_id,  1     as parent_id, 'PROD2'    as prod_name from dual union
  select 9  as prod_id,  8     as parent_id, 'PROD22'   as prod_name from dual union
  select 10 as prod_id,  null  as parent_id, 'ROOT2'    as prod_name from dual union
  select 11 as prod_id,  10    as parent_id, 'PROD22'   as prod_name from dual 
),
product_tree as (
  select 
    prod_id, parent_id, prod_name,
    connect_by_root p.prod_id              as root_id,
    sys_connect_by_path(prod_name, '|')    as prod_path,
    sys_connect_by_path(prod_id, '|')      as unique_path,
    level                                  as lvl,
    count(*) over (partition by sys_connect_by_path(prod_name, '|')) as qty
  from prods p start with parent_id is null
  connect by prior prod_id = parent_id
),
product_tree_branch (prod_id, parent_id, root_id, prod_name, lvl, prod_path, qty, branch) as (
  select 
    prod_id, parent_id, root_id, prod_name, lvl, prod_path, qty, 'x' branch
  from product_tree
  where parent_id is null
  union all
  select 
    t.prod_id, t.parent_id, t.root_id, t.prod_name, t.lvl, t.prod_path, t.qty, 
    case when (p.branch = 'x' and t.qty > 1) then t.unique_path else p.branch end branch
  from product_tree t
  join product_tree_branch p on p.prod_id = t.parent_id
),
super_tree as (
  select br.branch as branch_id, t.* 
  from product_tree_branch t
  join (select distinct root_id, branch from product_tree_branch) br on t.root_id = br.root_id and t.branch in (br.branch, 'x')
)
select
  rpad(' ', (lvl-1)*2, ' ')||t.prod_name as pad,
  t.*
from 
  super_tree t
order by branch_id, prod_path;


...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39471840
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarov,

А какую задачу ты сейчас решаешь?
Получить для каждого листа всех его родителей?

Тогда можно взять мой запрос тут 20524387 и добавить в него выделенное.

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
with prods as (
  select 1  as prod_id,  null  as parent_id, 'ROOT1'    as prod_name from dual union
  select 2  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 3  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 4  as prod_id,  1     as parent_id, 'PROD12'   as prod_name from dual union
  select 5  as prod_id,  2     as parent_id, 'PROD121'  as prod_name from dual union
  select 6  as prod_id,  3     as parent_id, 'PROD121'  as prod_name from dual union
  select 7  as prod_id,  4     as parent_id, 'PROD121'  as prod_name from dual union
  select 50 as prod_id,  2     as parent_id, 'PROD122'  as prod_name from dual union
  select 60 as prod_id,  3     as parent_id, 'PROD122'  as prod_name from dual union
  select 70 as prod_id,  4     as parent_id, 'PROD123'  as prod_name from dual union
  select 8  as prod_id,  1     as parent_id, 'PROD2'    as prod_name from dual union
  select 9  as prod_id,  8     as parent_id, 'PROD22'   as prod_name from dual union
  select 10 as prod_id,  null  as parent_id, 'ROOT2'    as prod_name from dual union
  select 11 as prod_id,  10    as parent_id, 'PROD22'   as prod_name from dual
),
product_tree as (
select
  prod_id,
  parent_id,
  connect_by_root p.prod_id                 as root_id,
  connect_by_root p.prod_name               as root_prod,
  p.prod_name                               as prod_name,
  rpad(' ', (level-1)*2, ' ')||p.prod_name  as prod_name_pad,
  sys_connect_by_path(p.prod_name, '|')     as prod_path,
  --
  sys_connect_by_path(p.prod_id, '|')||'|'  as unique_path,
  connect_by_isleaf                         as isleaf
from prods p
start with parent_id is null
connect by prior prod_id = parent_id),
super_tree as (
select pt1.prod_id leaf_id, pt2.*
  from product_tree pt1
  left join product_tree pt2
    on pt1.unique_path like pt2.unique_path || '%'
 where pt1.isleaf = 1)
select * from super_tree;


Семь листьев глубины три и один глубины два итого 7*3+1*2=23 строки. Это ожидается?
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472054
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshoprpovarov,
А какую задачу ты сейчас решаешь?
Получить для каждого листа всех его родителей?
...
Тогда можно взять мой запрос тут 20524387 и добавить в него выделенное.
Семь листьев глубины три и один глубины два итого 7*3+1*2=23 строки. Это ожидается?
Если идти от листьев, то каждый лист генерирует свою собственную ветку, это неправильно. В последнем примере продукты PROD121 и PROD122 оба являются листьями и находятся в одной ветке на одинаковом уровне под общим родителем PROD12. Они так и должны остаться вместе в одной ветке.

Т.е. должно получиться не две:
Код: plaintext
1.
2.
3.
4.
5.
6.
ROOT1
  PROD12
    PROD121
ROOT1
  PROD12
    PROD122

а одна:
Код: plaintext
1.
2.
3.
4.
ROOT1
  PROD12
    PROD121
    PROD122

В примере три экземпляра продукта PROD12, они ближе всего к корню, значит с них и начинаем делить. В результате должно получиться три полные ветви для каждого из PROD12, и одна "дефолтная" для остальных продуктов, не попадающих в те три ветви.

Вот ещё один боевой пример прямо из базы (привязок нет, NDA не нарушается), в нём куча "листьев"
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
select '1-1WWDY9C' prod_id,  null parent_id, 'PR0103' prod_name from dual union
select '1-1WWDY9G' prod_id,  '1-1WWDY9C' parent_id, 'OP0800' prod_name from dual union
select '1-1WWDY9O' prod_id,  '1-1WWDY9C' parent_id, 'OP0807' prod_name from dual union
select '1-1WWDYA2' prod_id,  '1-1WWDY9O' parent_id, 'OP0801' prod_name from dual union
select '1-1WWDYAF' prod_id,  '1-1WWDYA2' parent_id, 'ZR0132' prod_name from dual union
select '1-1WWDYAN' prod_id,  '1-1WWDYA2' parent_id, 'OP0804' prod_name from dual union
select '1-1WWDYAT' prod_id,  '1-1WWDYAN' parent_id, 'OP0002' prod_name from dual union
select '1-986USNA' prod_id,  '1-1WWDYA2' parent_id, '174079' prod_name from dual union
select '1-EWBDD75' prod_id,  '1-1WWDYA2' parent_id, 'OP1223' prod_name from dual union
select '1-L7G9LAI' prod_id,  '1-1WWDYA2' parent_id, 'CPSZ0120' prod_name from dual union
select '1-L7G9LAM' prod_id,  '1-L7G9LAI' parent_id, 'CPSN0096' prod_name from dual union
select '1-1WWDYBG' prod_id,  '1-1WWDY9O' parent_id, 'OP0801' prod_name from dual union
select '1-1WWDYCB' prod_id,  '1-1WWDYBG' parent_id, 'ZR0132' prod_name from dual union
select '1-1WWDYCF' prod_id,  '1-1WWDYBG' parent_id, 'OP0804' prod_name from dual union
select '1-1WWDYCL' prod_id,  '1-1WWDYCF' parent_id, 'OP0002' prod_name from dual union
select '1-EWBDD79' prod_id,  '1-1WWDYBG' parent_id, 'OP1223' prod_name from dual union
select '1-IYJ8E7W' prod_id,  '1-1WWDYBG' parent_id, '174076' prod_name from dual union
select '1-L7G9LAQ' prod_id,  '1-1WWDYBG' parent_id, 'CPSZ0120' prod_name from dual union
select '1-L7G9LAU' prod_id,  '1-L7G9LAQ' parent_id, 'CPSN0096' prod_name from dual union
select '1-IYJ8E8E' prod_id,  '1-1WWDY9C' parent_id, 'OP1725' prod_name from dual



Если его подставить в твой пример, то листья сгенерируют 10+ веток, а их на самом деле всего три (две для продуктов OP0801, и одна общая).
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472098
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovВ примереВроде мой последний запрос возвращает то же количество строк, что и твой последний.

Можно, конечно, допилить с учетом новых хотелок, но есть подозрение, что ты уже пытаешься реализовать то, что делать не надо.

В конце концов, тебе виднее, что за логику надо реализовать. Если будут конкретные вопросы - спрашивай.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472152
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshopВроде мой последний запрос возвращает то же количество строк, что и твой последний.

Если подставить "боевой", то количество строк разное, в твоём больше (48 против 28). Но количество строк в принципе не так важно, основная проблема в том, что ветки разбиваются и потом не совпадают с map-таблицей.

dbms_photoshopМожно, конечно, допилить с учетом новых хотелок, но есть подозрение, что ты уже пытаешься реализовать то, что делать не надо.
В конце концов, тебе виднее, что за логику надо реализовать. Если будут конкретные вопросы - спрашивай.
Последняя версия кода вроде делает всё что нужно. По крайней мере, пока ещё не наткнулся на вариант, где она лажает. Благодаря твоим вариантам и моим попыткам их доработать нашлись и были исправлены другие местные косяки в данных и map-таблицах :)

Вопрос, собственно, такой - можно ли как-то совместить эти две конструкции в одну (и есть ли в этом смысл с точки зрения оптимизации)? В первой считаются пути и их количество для выявления нескольких экземпляров одного продукта, во второй на основании этого количества генерируется параметр branch.
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
...
product_tree as (
  select 
    prod_id, parent_id, prod_name,
    connect_by_root p.prod_id              as root_id,
    sys_connect_by_path(prod_name, '|')    as prod_path,
    sys_connect_by_path(prod_id, '|')      as unique_path,
    level                                  as lvl,
    count(*) over (partition by sys_connect_by_path(prod_name, '|')) as qty
  from prods p start with parent_id is null
  connect by prior prod_id = parent_id
),
product_tree_branch (prod_id, parent_id, root_id, prod_name, lvl, prod_path, qty, branch) as (
  select 
    prod_id, parent_id, root_id, prod_name, lvl, prod_path, qty, 'x' branch
  from product_tree
  where parent_id is null
  union all
  select 
    t.prod_id, t.parent_id, t.root_id, t.prod_name, t.lvl, t.prod_path, t.qty, 
    case when (p.branch = 'x' and t.qty > 1) then t.unique_path else p.branch end branch
  from product_tree t
  join product_tree_branch p on p.prod_id = t.parent_id
),
...



И ещё один вопрос, который тут не обсуждался, но связан с темой. В части, выделенной красным цветом - я правильно надеюсь на short-circuit evaluation, и при выполнении условия pm.param_key is null Оракл не пойдёт дальше и не будет выполнять селект из второго условия?

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
                          select pt.*,
                               pm.id, pm.type, pm.target_product_code, pm.target_entity_type, pm.duplication_group, pm.duplication_depth, pm.cnt, pm.position,
                               count(pm.prod_path) over(partition by pt.root_row_id, pt.branch_id, pm.id) cnt_root_map
                          from super_tree pt
                          left join V_MAP_FIX_MIGRATION_TREE pm
                            on  pt.prod_path = pm.prod_path 
                            and (
                                  -- non-parametrized product
                                  ( pm.param_key is null ) 
                                  OR 
                                  -- parametrized product, check asset attribute
                                  ( pm.param_value is not null 
                                    and pm.param_value = 
                                    ( select /*+ index(pr) */ char_val from L0_CLD_FIX_PROD_PARAMS pr where asset_id = pt.row_id and attr_name = pm.param_key ) 
                                  ) 
                                )
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472213
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarovВопрос, собственно, такой - можно ли как-то совместить эти две конструкции в однуОтвет: rec with там вообще не нужен.
rpovarovЕсли идти от листьев, то каждый лист генерирует свою собственную ветку, это неправильно. В последнем примере продукты PROD121 и PROD122 оба являются листьями и находятся в одной ветке на одинаковом уровне под общим родителем PROD12. Они так и должны остаться вместе в одной ветке.Что значит на одной "ветке"?
Имеется в виду пути до листьев полностью совпадают?
Ну так это поведение по умолчанию connect by выведет всех родителей по разу и каждый лист по разу и ты получаешь.
rpovarov
Код: plaintext
1.
2.
3.
4.
ROOT1
  PROD12
    PROD121
    PROD122
Непонятно уже надо тебе что-то размножать или нет и если надо, то при каких условиях.
Когда ты это ясно сможешь для себя сформулировать, тогда и запрос сам собой напишется.
rpovarovВопрос, собственно, такой - можно ли как-то совместить эти две конструкции в однуЛюбители абсолютной строгости рекомендуют прибегать к case. 20384658
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472222
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В последней цитате должен быть второй вопрос.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472229
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshopНепонятно уже надо тебе что-то размножать или нет и если надо, то при каких условиях.
Когда ты это ясно сможешь для себя сформулировать, тогда и запрос сам собой напишется.
Ну я же привёл рабочий пример в 20563070 . Этот код делает сейчас именно то, что нужно. В случае нескольких экземпляров одного и того же продукта генерируются полные ветки для них и обозначаются идентификатором branch_id.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472292
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarov,

Ммм... то, что делает твой запрос понятно только тебе.
Хотя, подозреваю, если ты посмотришь на него через год и тебе будет вообще непонятно что это за 'x' и что курил аффтар.

Код: plaintext
1.
2.
select prod_id, parent_id, prod_path, branch_id
from ...
order by prod_path, prod_id, parent_id, branch_id

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
   PROD_ID  PARENT_ID PROD_PATH                      BRANCH_ID 
---------- ---------- ------------------------------ ----------
         1            |ROOT1                         x         
         1            |ROOT1                         |1|2      
         1            |ROOT1                         |1|3      
         1            |ROOT1                         |1|4      
         2          1 |ROOT1|PROD12                  |1|2      
         3          1 |ROOT1|PROD12                  |1|3      
         4          1 |ROOT1|PROD12                  |1|4      
         5          2 |ROOT1|PROD12|PROD121          |1|2      
         6          3 |ROOT1|PROD12|PROD121          |1|3      
         7          4 |ROOT1|PROD12|PROD121          |1|4      
        50          2 |ROOT1|PROD12|PROD122          |1|2      
        60          3 |ROOT1|PROD12|PROD122          |1|3      
        70          4 |ROOT1|PROD12|PROD123          |1|4      
         8          1 |ROOT1|PROD2                   x         
         8          1 |ROOT1|PROD2                   |1|2      
         8          1 |ROOT1|PROD2                   |1|3      
         8          1 |ROOT1|PROD2                   |1|4      
         9          8 |ROOT1|PROD2|PROD22            x         
         9          8 |ROOT1|PROD2|PROD22            |1|2      
         9          8 |ROOT1|PROD2|PROD22            |1|3      
         9          8 |ROOT1|PROD2|PROD22            |1|4      
        10            |ROOT2                         x         
        11         10 |ROOT2|PROD22                  x         

23 rows selected.

Зачем продукт с ид 7 и 8 продублированы по 4 раза?
Почему продукт с ид 1 тоже продублирован именно 4 раза?
Почему ветку "идентифицируют" два айдишника независимо от глубины.
и т. д.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472325
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshop,

Если отсортировать по другому, то так понятнее.

Код: plaintext
PADBRANCH_IDPROD_IDPROD_PATHROOT1x1|ROOT1 PROD2x8|ROOT1|PROD2 PROD22x9|ROOT1|PROD2|PROD22ROOT2x10|ROOT2 PROD22x11|ROOT2|PROD22ROOT1|1|21|ROOT1 PROD12|1|22|ROOT1|PROD12 PROD121|1|25|ROOT1|PROD12|PROD121 PROD122|1|250|ROOT1|PROD12|PROD122 PROD2|1|28|ROOT1|PROD2 PROD22|1|29|ROOT1|PROD2|PROD22ROOT1|1|31|ROOT1 PROD12|1|33|ROOT1|PROD12 PROD121|1|36|ROOT1|PROD12|PROD121 PROD122|1|360|ROOT1|PROD12|PROD122 PROD2|1|38|ROOT1|PROD2 PROD22|1|39|ROOT1|PROD2|PROD22ROOT1|1|41|ROOT1 PROD12|1|44|ROOT1|PROD12 PROD121|1|47|ROOT1|PROD12|PROD121 PROD123|1|470|ROOT1|PROD12|PROD123 PROD2|1|48|ROOT1|PROD2 PROD22|1|49|ROOT1|PROD2|PROD22
Продукты 8 (PROD2) и 9 (PROD22) не принадлежали веткам с multi-instance продуктами, но они могут быть важны для идентификации службы. Например, если служба описана как комбинация продуктов ROOT1, PROD12, PROD121, PROD122, PROD2, то, не включив PROD2, я теряю всю службу.

Дупликаты продуктов не мешают, они отфильтровываются на конечном этапе.

Ветка идентифицируется через unique_path продукта с multi-instance, ближайшего к корню. В примере это PROD12, он на втором уровне, поэтому branch_id состоит из двух элементов. Если разветвление будет на третьем уровне, то branch_id будет из трёх элементов ("боевой" пример как раз такой).
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472328
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshopХотя, подозреваю, если ты посмотришь на него через год и тебе будет вообще непонятно что это за 'x' и что курил аффтар.


Борюсь с этим явлением щедро рассыпая комментарии и пояснения в коде :)
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472528
Фотография dbms_photoshop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rpovarov,

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

Учитывая сказанное "размножение" опять таки достигается путем self join только условие соединения более замысловатое.

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
select pt1.prod_name_pad pad,
       pt2.unique_path branch,
       pt1.prod_id,
       pt1.prod_path
  -- for lvl 2 branches
  from (select * from product_tree where lvl = 2) pt2
  join product_tree pt1
       -- within the same root
    on pt1.root_id = pt2.root_id and
       (
       -- replicate children
       pt1.unique_path like pt2.unique_path || '%'
       -- replicate roots
       or pt1.lvl = 1
       -- replicate nodes from other bracnges when following conditions are met
       or (pt1.cnt = 1 and pt2.cnt <> 1 and not pt1.prod_path like pt2.prod_path || '%')
       )
order by pt2.unique_path, pt1.prod_path;



Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
PAD                  BRANCH        PROD_ID PROD_PATH                     
-------------------- ---------- ---------- ------------------------------
ROOT2                |10|11|            10 |ROOT2                        
  PROD22             |10|11|            11 |ROOT2|PROD22                 
ROOT1                |1|2|               1 |ROOT1                        
  PROD12             |1|2|               2 |ROOT1|PROD12                 
    PROD121          |1|2|               5 |ROOT1|PROD12|PROD121         
    PROD122          |1|2|              50 |ROOT1|PROD12|PROD122         
  PROD2              |1|2|               8 |ROOT1|PROD2                  
    PROD22           |1|2|               9 |ROOT1|PROD2|PROD22           
ROOT1                |1|3|               1 |ROOT1                        
  PROD12             |1|3|               3 |ROOT1|PROD12                 
    PROD121          |1|3|               6 |ROOT1|PROD12|PROD121         
    PROD122          |1|3|              60 |ROOT1|PROD12|PROD122         
  PROD2              |1|3|               8 |ROOT1|PROD2                  
    PROD22           |1|3|               9 |ROOT1|PROD2|PROD22           
ROOT1                |1|4|               1 |ROOT1                        
  PROD12             |1|4|               4 |ROOT1|PROD12                 
    PROD121          |1|4|               7 |ROOT1|PROD12|PROD121         
    PROD123          |1|4|              70 |ROOT1|PROD12|PROD123         
  PROD2              |1|4|               8 |ROOT1|PROD2                  
    PROD22           |1|4|               9 |ROOT1|PROD2|PROD22           
ROOT1                |1|8|               1 |ROOT1                        
  PROD2              |1|8|               8 |ROOT1|PROD2                  
    PROD22           |1|8|               9 |ROOT1|PROD2|PROD22           

23 rows selected.
...
Рейтинг: 0 / 0
Поиск комбинаций строк в таблице
    #39472554
rpovarov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dbms_photoshoprpovarov,
У тебя логика завязана на второй уровень и не надо создавать иллюзии, что размножение будет корректным при любой ветвистости и дубликатных путях.


У меня как раз уровень вычисляется динамически, берётся первый, где нашлись дубликаты. Для этого и делался второй проход with rec. Например, вот ветвление на третьем (|PR0103|OP0807|OP0801):
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
with prods as (
select '1-1WWDY9C' prod_id,  null parent_id, 'PR0103' prod_name from dual union
select '1-1WWDY9G' prod_id,  '1-1WWDY9C' parent_id, 'OP0800' prod_name from dual union
select '1-1WWDY9O' prod_id,  '1-1WWDY9C' parent_id, 'OP0807' prod_name from dual union
select '1-1WWDYA2' prod_id,  '1-1WWDY9O' parent_id, 'OP0801' prod_name from dual union
select '1-1WWDYAF' prod_id,  '1-1WWDYA2' parent_id, 'ZR0132' prod_name from dual union
select '1-1WWDYAN' prod_id,  '1-1WWDYA2' parent_id, 'OP0804' prod_name from dual union
select '1-1WWDYAT' prod_id,  '1-1WWDYAN' parent_id, 'OP0002' prod_name from dual union
select '1-986USNA' prod_id,  '1-1WWDYA2' parent_id, '174079' prod_name from dual union
select '1-EWBDD75' prod_id,  '1-1WWDYA2' parent_id, 'OP1223' prod_name from dual union
select '1-L7G9LAI' prod_id,  '1-1WWDYA2' parent_id, 'CPSZ0120' prod_name from dual union
select '1-L7G9LAM' prod_id,  '1-L7G9LAI' parent_id, 'CPSN0096' prod_name from dual union
select '1-1WWDYBG' prod_id,  '1-1WWDY9O' parent_id, 'OP0801' prod_name from dual union
select '1-1WWDYCB' prod_id,  '1-1WWDYBG' parent_id, 'ZR0132' prod_name from dual union
select '1-1WWDYCF' prod_id,  '1-1WWDYBG' parent_id, 'OP0804' prod_name from dual union
select '1-1WWDYCL' prod_id,  '1-1WWDYCF' parent_id, 'OP0002' prod_name from dual union
select '1-EWBDD79' prod_id,  '1-1WWDYBG' parent_id, 'OP1223' prod_name from dual union
select '1-IYJ8E7W' prod_id,  '1-1WWDYBG' parent_id, '174076' prod_name from dual union
select '1-L7G9LAQ' prod_id,  '1-1WWDYBG' parent_id, 'CPSZ0120' prod_name from dual union
select '1-L7G9LAU' prod_id,  '1-L7G9LAQ' parent_id, 'CPSN0096' prod_name from dual union
select '1-IYJ8E8E' prod_id,  '1-1WWDY9C' parent_id, 'OP1725' prod_name from dual 
),
product_tree as (
  select 
    prod_id, parent_id, prod_name,
    connect_by_root p.prod_id              as root_id,
    sys_connect_by_path(prod_name, '|')    as prod_path,
    sys_connect_by_path(prod_id, '|')      as unique_path,
    level                                  as lvl,
    count(*) over (partition by sys_connect_by_path(prod_name, '|')) as qty
  from prods p start with parent_id is null
  connect by prior prod_id = parent_id
),
product_tree_branch (prod_id, parent_id, root_id, prod_name, lvl, prod_path, qty, branch) as (
  select 
    prod_id, parent_id, root_id, prod_name, lvl, prod_path, qty, 'x' branch
  from product_tree
  where parent_id is null
  union all
  select 
    t.prod_id, t.parent_id, t.root_id, t.prod_name, t.lvl, t.prod_path, t.qty, 
    case when (p.branch = 'x' and t.qty > 1) then t.unique_path else p.branch end branch
  from product_tree t
  join product_tree_branch p on p.prod_id = t.parent_id
),
super_tree as (
  select br.branch as branch_id, t.* 
  from product_tree_branch t
  join (select distinct root_id, branch from product_tree_branch) br on t.root_id = br.root_id and t.branch in (br.branch, 'x')
)
select
  rpad(' ', (lvl-1)*2, ' ')||t.prod_name as pad,
  branch_id, prod_id, prod_path
from 
  super_tree t
order by branch_id, prod_path; 


Код: plaintext
PADBRANCH_IDPROD_IDPROD_PATHPR0103x1-1WWDY9C|PR0103 OP0800x1-1WWDY9G|PR0103|OP0800 OP0807x1-1WWDY9O|PR0103|OP0807 OP1725x1-IYJ8E8E|PR0103|OP1725PR0103|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-1WWDY9C|PR0103 OP0800|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-1WWDY9G|PR0103|OP0800 OP0807|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-1WWDY9O|PR0103|OP0807 OP0801|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-1WWDYA2|PR0103|OP0807|OP0801 CPSZ0120|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-L7G9LAI|PR0103|OP0807|OP0801|CPSZ0120 CPSN0096|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-L7G9LAM|PR0103|OP0807|OP0801|CPSZ0120|CPSN0096 OP0804|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-1WWDYAN|PR0103|OP0807|OP0801|OP0804 OP0002|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-1WWDYAT|PR0103|OP0807|OP0801|OP0804|OP0002 OP1223|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-EWBDD75|PR0103|OP0807|OP0801|OP1223 ZR0132|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-1WWDYAF|PR0103|OP0807|OP0801|ZR0132 174079|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-986USNA|PR0103|OP0807|OP0801|174079 OP1725|1-1WWDY9C|1-1WWDY9O|1-1WWDYA21-IYJ8E8E|PR0103|OP1725PR0103|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-1WWDY9C|PR0103 OP0800|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-1WWDY9G|PR0103|OP0800 OP0807|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-1WWDY9O|PR0103|OP0807 OP0801|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-1WWDYBG|PR0103|OP0807|OP0801 CPSZ0120|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-L7G9LAQ|PR0103|OP0807|OP0801|CPSZ0120 CPSN0096|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-L7G9LAU|PR0103|OP0807|OP0801|CPSZ0120|CPSN0096 OP0804|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-1WWDYCF|PR0103|OP0807|OP0801|OP0804 OP0002|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-1WWDYCL|PR0103|OP0807|OP0801|OP0804|OP0002 OP1223|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-EWBDD79|PR0103|OP0807|OP0801|OP1223 ZR0132|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-1WWDYCB|PR0103|OP0807|OP0801|ZR0132 174076|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-IYJ8E7W|PR0103|OP0807|OP0801|174076 OP1725|1-1WWDY9C|1-1WWDY9O|1-1WWDYBG1-IYJ8E8E|PR0103|OP1725

Можно даже оба набора исходных данных (со вторым и третьим уровнем) слить воедино, и скрипт правильно отработает:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
with prods as (
select '1-1WWDY9C' prod_id,  null parent_id, 'PR0103' prod_name from dual union
select '1-1WWDY9G' prod_id,  '1-1WWDY9C' parent_id, 'OP0800' prod_name from dual union
select '1-1WWDY9O' prod_id,  '1-1WWDY9C' parent_id, 'OP0807' prod_name from dual union
select '1-1WWDYA2' prod_id,  '1-1WWDY9O' parent_id, 'OP0801' prod_name from dual union
select '1-1WWDYAF' prod_id,  '1-1WWDYA2' parent_id, 'ZR0132' prod_name from dual union
select '1-1WWDYAN' prod_id,  '1-1WWDYA2' parent_id, 'OP0804' prod_name from dual union
select '1-1WWDYAT' prod_id,  '1-1WWDYAN' parent_id, 'OP0002' prod_name from dual union
select '1-986USNA' prod_id,  '1-1WWDYA2' parent_id, '174079' prod_name from dual union
select '1-EWBDD75' prod_id,  '1-1WWDYA2' parent_id, 'OP1223' prod_name from dual union
select '1-L7G9LAI' prod_id,  '1-1WWDYA2' parent_id, 'CPSZ0120' prod_name from dual union
select '1-L7G9LAM' prod_id,  '1-L7G9LAI' parent_id, 'CPSN0096' prod_name from dual union
select '1-1WWDYBG' prod_id,  '1-1WWDY9O' parent_id, 'OP0801' prod_name from dual union
select '1-1WWDYCB' prod_id,  '1-1WWDYBG' parent_id, 'ZR0132' prod_name from dual union
select '1-1WWDYCF' prod_id,  '1-1WWDYBG' parent_id, 'OP0804' prod_name from dual union
select '1-1WWDYCL' prod_id,  '1-1WWDYCF' parent_id, 'OP0002' prod_name from dual union
select '1-EWBDD79' prod_id,  '1-1WWDYBG' parent_id, 'OP1223' prod_name from dual union
select '1-IYJ8E7W' prod_id,  '1-1WWDYBG' parent_id, '174076' prod_name from dual union
select '1-L7G9LAQ' prod_id,  '1-1WWDYBG' parent_id, 'CPSZ0120' prod_name from dual union
select '1-L7G9LAU' prod_id,  '1-L7G9LAQ' parent_id, 'CPSN0096' prod_name from dual union
select '1-IYJ8E8E' prod_id,  '1-1WWDY9C' parent_id, 'OP1725' prod_name from dual union
  select '1'  as prod_id,  null    as parent_id, 'ROOT1'    as prod_name from dual union
  select '2'  as prod_id,  '1'     as parent_id, 'PROD12'   as prod_name from dual union
  select '3'  as prod_id,  '1'     as parent_id, 'PROD12'   as prod_name from dual union
  select '4'  as prod_id,  '1'     as parent_id, 'PROD12'   as prod_name from dual union
  select '5'  as prod_id,  '2'     as parent_id, 'PROD121'  as prod_name from dual union
  select '6'  as prod_id,  '3'     as parent_id, 'PROD121'  as prod_name from dual union
  select '7'  as prod_id,  '4'     as parent_id, 'PROD121'  as prod_name from dual union
  select '50' as prod_id,  '2'     as parent_id, 'PROD122'  as prod_name from dual union
  select '60' as prod_id,  '3'     as parent_id, 'PROD122'  as prod_name from dual union
  select '70' as prod_id,  '4'     as parent_id, 'PROD123'  as prod_name from dual union
  select '8'  as prod_id,  '1'     as parent_id, 'PROD2'    as prod_name from dual union
  select '9'  as prod_id,  '8'     as parent_id, 'PROD22'   as prod_name from dual union
  select '10' as prod_id,  null    as parent_id, 'ROOT2'    as prod_name from dual union
  select '11' as prod_id,  '10'    as parent_id, 'PROD22'   as prod_name from dual
),
product_tree as (
  select 
    prod_id, parent_id, prod_name,
    connect_by_root p.prod_id              as root_id,
    sys_connect_by_path(prod_name, '|')    as prod_path,
    sys_connect_by_path(prod_id, '|')      as unique_path,
    level                                  as lvl,
    count(*) over (partition by sys_connect_by_path(prod_name, '|')) as qty
  from prods p start with parent_id is null
  connect by prior prod_id = parent_id
),
product_tree_branch (prod_id, parent_id, root_id, prod_name, lvl, prod_path, qty, branch) as (
  select 
    prod_id, parent_id, root_id, prod_name, lvl, prod_path, qty, 'x' branch
  from product_tree
  where parent_id is null
  union all
  select 
    t.prod_id, t.parent_id, t.root_id, t.prod_name, t.lvl, t.prod_path, t.qty, 
    case when (p.branch = 'x' and t.qty > 1) then t.unique_path else p.branch end branch
  from product_tree t
  join product_tree_branch p on p.prod_id = t.parent_id
),
super_tree as (
  select br.branch as branch_id, t.* 
  from product_tree_branch t
  join (select distinct root_id, branch from product_tree_branch) br on t.root_id = br.root_id and t.branch in (br.branch, 'x')
)
select
  rpad(' ', (lvl-1)*2, ' ')||t.prod_name as prod_name_pad,
  branch_id, prod_id, prod_path
from 
  super_tree t
order by branch_id, prod_path; 

...
Рейтинг: 0 / 0
40 сообщений из 40, показаны все 2 страниц
Форумы / Oracle [игнор отключен] [закрыт для гостей] / Поиск комбинаций строк в таблице
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]