使用 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;
/
我有两个表:
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;
/