Использование SQL-запроса или функции для рекурсивного отслеживания слияний - PullRequest
0 голосов
/ 16 ноября 2018

У меня есть две таблицы:

create table person (
person_id number,
name varchar2(20)
);
insert into person (person_id, name) values (1, 'Dan');
insert into person (person_id, name) values (2, 'Tom');
insert into person (person_id, name) values (3, 'Tim');
insert into person (person_id, name) values (4, 'Bob');
insert into person (person_id, name) values (5, 'Pat');
insert into person (person_id, name) values (6, 'Ted');
create table person_merge (
person_id number,
person_merged_to_id number
);
insert into person_merge (person_id, person_merged_to_id) values (2, 3);
insert into person_merge (person_id, person_merged_to_id) values (3, 4);
insert into person_merge (person_id, person_merged_to_id) values (5, 6);
insert into person_merge (person_id, person_merged_to_id) values (6, 5);

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

map (1, 2, 3, 4) => (1, 4, 4, 4)
map (1, 2, 3, 4, 5) => cyclical merge error

Как я мог сделать это чисто? С CTE, CONNECT BY или рекурсивной функцией?

1 Ответ

0 голосов
/ 16 ноября 2018

Вот функция, которая выполняет поиск списка последнего человека

CREATE OR REPLACE FUNCTION findTheLastPersonList(
   pi_startPersonList sys.odcinumberlist
)
RETURN sys.odcinumberlist
AS
  lv_lastPersonList sys.odcinumberlist := new sys.odcinumberlist();

  /* Get the last merged person in a chain
   *
   * @param pi_startPersonId  Person from which searching should be started
   * @return                  Id of the last person in a chain or  pi_startPersonId in case person is merged to nobody
   */   
  FUNCTION findTheLastPerson(
      pi_startPersonId NUMBER
  )
  RETURN NUMBER
  AS
    lv_laslPersonId NUMBER;
  BEGIN

    BEGIN
      WITH t1(person_id, person_merged_to_id, lvl) AS (
        -- Anchor member.
        SELECT person_id,
               person_merged_to_id,
               1 AS lvl
          FROM person_merge
         WHERE person_id = pi_startPersonId
         UNION ALL
        -- Recursive member.
        SELECT t2.person_id,
               t2.person_merged_to_id,
               lvl+1
          FROM person_merge t2, t1
         WHERE t2.person_id = t1.person_merged_to_id
      )
      SELECT person_merged_to_id
        INTO lv_laslPersonId
        FROM (
              SELECT person_id,
                     person_merged_to_id,
                     lvl,
                     max(lvl) OVER () AS max_lvl
              FROM   t1
             ) t
       WHERE t.lvl = t.max_lvl;
     EXCEPTION
       WHEN NO_DATA_FOUND THEN
         lv_laslPersonId := pi_startPersonId;
     END;

     RETURN lv_laslPersonId;

  END findTheLastPerson;

BEGIN
  FOR i IN 1..pi_startPersonList.Count LOOP
    lv_lastPersonList.Extend();
    lv_lastPersonList(i) := findTheLastPerson(pi_startPersonId => pi_startPersonList(i));
  END LOOP;
  RETURN lv_lastPersonList;
END findTheLastPersonList;

Вот пример вызова списка, для которого не возникает исключение

DECLARE
  lv_toFind sys.odcinumberlist := sys.odcinumberlist(1, 2, 3, 4);
  lv_result sys.odcinumberlist;
BEGIN
  lv_result := findTheLastPersonList(
                   pi_startPersonList => lv_toFind
               );

  FOR i IN 1..lv_result.COUNT LOOP
    dbms_output.put_line(lv_toFind(i) || ' => ' || lv_result(i));
  END LOOP;
END;
/

И пример вызова для случая, когда выбрасывается исключение

DECLARE
  lv_toFind sys.odcinumberlist := sys.odcinumberlist(1, 2, 3, 4, 5);
  lv_result sys.odcinumberlist;
BEGIN
  lv_result := findTheLastPersonList(
                   pi_startPersonList => lv_toFind
               );

  FOR i IN 1..lv_result.COUNT LOOP
    dbms_output.put_line(lv_toFind(i) || ' => ' || lv_result(i));
  END LOOP;
END;
/
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...