Поскольку вы используете 10g, а не более новую версию, вам нужно использовать иерархические запросы oracle вместо рекурсивных общих табличных выражений, показанных The Impaler. Чтобы мое решение работало, полезно кодировать пол животного в виде отдельного столбца, а не встраивать его в идентификатор животного, поэтому я буду использовать следующее определение таблицы. (Примечание: у меня нет экземпляра 10g, чтобы попробовать это, поэтому я не знаю наверняка, доступны ли снимаемые ограничения в 10g или нет. Если не просто удалить эти предложения. Они упростили загрузку данных образца.) :
CREATE TABLE animal
( ID number not null primary key
, GENDER varchar2(1) not null
, SIRE_ID number
, DAM_ID number
, constraint animal_gender check (gender in ('M','F'))
, constraint animal_sire_fk FOREIGN KEY (sire_id) REFERENCES animal(id) DEFERRABLE INITIALLY DEFERRED
, constraint animal_dam_fk FOREIGN KEY (dam_id) REFERENCES animal(id) DEFERRABLE INITIALLY DEFERRED
);
Отсюда полезно сгенерировать отображение всех предков для любого данного животного, это называется закрывающей таблицей, и при желании вы можете получить дополнительную информацию о закрывающих таблицах в Google. Это делается либо с рекурсивными SQL, либо в нашем случае с oracle иерархическими таблицами, поскольку вы используете 10g. Я назову его «Родословная» для этого примера:
with ancestry as (
select CONNECT_BY_ROOT id id
, CONNECT_BY_ROOT gender gender
, id ancestor_id
, gender ancestor_gender
, level-1 lvl
from animal
connect by id in (prior sire_id, prior dam_id)
)
Отсюда вы можете найти всех животных с общим происхождением с помощью умеренно простого соединения:
select m.id sire_id
, f.id dam_id
, m.ancestor_id
, m.ancestor_gender
, m.lvl sire_lvl
, f.lvl dam_lvl
from ancestry m
join ancestry f
on m.ancestor_id = f.ancestor_id
and m.gender = 'M'
and f.gender = 'F';
В этом запросе перечислены все пары животные мужского и женского пола и все их общие предки. Это многовато, и мы хотим сократить его до только тех пар, которые вам интересны, и ограничить его только первым общим предком. Для этого мы добавим предложение where, ограничивающее его интересующей парой (парами), и будем использовать агрегаты, чтобы свести нас только к первому предку:
select m.id sire_id
, f.id dam_id
, max(m.ancestor_id) keep (dense_rank first order by least(m.lvl,f.lvl)) first_ancestor
, max(m.ancestor_gender) keep (dense_rank first order by least(m.lvl,f.lvl)) ancestor_gnder
, min(m.lvl) sire_lvl
, min(f.lvl) dam_lvl
from ancestry m
join ancestry f
on m.ancestor_id = f.ancestor_id
and m.gender = 'M'
and f.gender = 'F'
where (m.id, f.id) in ((111,211))
group by m.id, f.id;
Собираем все вместе вот последний запрос :
with ancestry as (
select CONNECT_BY_ROOT id id
, CONNECT_BY_ROOT gender gender
, id ancestor_id
, gender ancestor_gender
, level-1 lvl
from animal
connect by id in (prior sire_id, prior dam_id)
)
select m.id sire_id
, f.id dam_id
, max(m.ancestor_id) keep (dense_rank first order by least(m.lvl,f.lvl)) first_ancestor
, max(m.ancestor_gender) keep (dense_rank first order by least(m.lvl,f.lvl)) ancestor_gnder
, min(m.lvl) sire_lvl
, min(f.lvl) dam_lvl
from ancestry m
join ancestry f
on m.ancestor_id = f.ancestor_id
and m.gender = 'M'
and f.gender = 'F'
where (m.id, f.id) in ((111,211))
group by m.id, f.id;
И вы можете увидеть это в действии с помощью этой db <> fiddle в примере скрипки данные о предках были сгенерированы с помощью example # в наиболее значимом месте идентификатора животного. , и только те предки, которые отличались от примера 1, были введены для примеров 2 и 3, поэтому для примера 2 была добавлена только мужская линия, а в примере 3 была добавлена только женская линия, что дало следующие пары (1111, 1121), (2111 , 1211) и (1111,3211) для примеров 1, 2 и 3. соответственно.
Это будет возвращать записи, только если пара животных имеет общих предков. Он также предварительно генерирует полное закрытие предков, что может занять много времени для больших списков смежности. Для более эффективного запроса закрытие предков может быть ограничено только интересующими животными с условием START. Кроме того, глубина поиска может быть ограничена в одном из двух мест (или в обоих): в предложении where в выходном запросе или в предложении where в запросе предков. Кроме того, чтобы удовлетворить ваше требование, чтобы пары возвращали строку, показывающую нулевой уровень, когда нет общих предков, требуются некоторые небольшие изменения. Во-первых, CTE предков необходимо изменить, чтобы он имел нулевой уровень для самосоединения (нулевая глубина). Это будет важно для работы агрегатов. Затем необходимо немного обновить агрегированные столбцы и критерии соединения, чтобы разрешить записи без общих предков. Вот модифицированный запрос:
with ancestry as (
select CONNECT_BY_ROOT id id
, CONNECT_BY_ROOT gender gender
, id ancestor_id
, gender ancestor_gender
, case level when 1 then null else level-1 end lvl
, level-1 lvl0
from animal
-- Limit depth to 3 generations
where level-1 <= 3
connect by id in (prior sire_id, prior dam_id)
-- Only build ancestry closure for these animals
start with id in (1111,1211,2111,3211)
)
select m.id sire_id
, f.id dam_id
, max(nvl2(m.lvl,m.ancestor_id,null)) keep (dense_rank first order by least(m.lvl,f.lvl) nulls last) first_ancestor
, max(nvl2(m.lvl,m.ancestor_gender,null)) keep (dense_rank first order by least(m.lvl,f.lvl) nulls last) ancestor_gnder
, nvl(min(m.lvl),0) sire_lvl
, nvl(min(f.lvl),0) dam_lvl
from ancestry m
join ancestry f
on (m.ancestor_id = f.ancestor_id or (m.id, f.id) in ((m.ancestor_id, f.ancestor_id)))
and m.gender = 'M'
and f.gender = 'F'
where (m.id, f.id) in ((1111,1211) -- First example no common ancestors
,(2111,1211) -- 2nd ex common ancesters in male line
,(1111,3211))-- 3rd ex common ancestry of sire & dam
-- Limit to at most 3 generations
and greatest(m.lvl0, f.lvl0) <= 3
group by m.id, f.id;