使用 SQL 查询或函数以递归方式跟踪合并

Using SQL query or function to trace merges recursively

我有两个表:

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 或递归函数?

这是一个搜索最后一个人列表的函数

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;

这里是一个没有发生异常的列表调用示例[​​=13=]

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;
/

以及抛出异常时的调用示例[​​=13=]

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;
/