Как рекурсивно создавать родословные и находить совпадения для обнаружения инбридинга (Oracle) - PullRequest
1 голос
/ 05 мая 2020

Я в тупике, создавая функцию в Oracle, чтобы узнать, могут ли 2 спаривающихся животного создать инбридинг. Функция должна принимать 3 параметра: мужской идентификатор, женский идентификатор и глубину для изучения.

Сначала я думаю, что я должен создать две родословные, используя данные из таблицы с такой структурой:

    TABLE animal
    +-----+---------+--------+
    | ID  | SIRE_ID | DAM_ID |
    +-----+---------+--------+
    | 111 | 112     | 212    |
    | 112 | 113     | 213    |
    | 212 | 116     | 216    |
    +-----+---------+--------+

(Не совсем актуально, но для этого и будущих примеров я использую идентификаторы, поскольку 1 - мужчина, а 2 - женщина.) Для этого я должен использовать параметр глубины - вероятно, рекурсивно.

Это то, что у меня есть:

function animal_pedigree (p_id number,
    p_max_pedigree_level number,
    p_pedigree_level number := 0,
    p_position varchar2 := '') return animal_ancestors_table
    pipelined
is
    v_sire_id number;
    v_dam_id number;
        v_row animal_ancestor;
begin
        v_row.id := p_id;
        v_row.pedigree_level := p_pedigree_level;
        v_row.position := p_position;
    pipe row (v_row);
    if p_pedigree_level < p_max_pedigree_level then
        select sire_id, dam_id
        into v_sire_id, v_dam_id
        from arc.animal
        where id = p_id;
        if v_sire_id is not null then
            for rec in (select id, pedigree_level, position
                from table(animal_pedigree (v_sire_id, p_max_pedigree_level, p_pedigree_level+1, p_position || 's'))) loop
                                v_row.id := rec.id;
                                v_row.pedigree_level := rec.pedigree_level;
                                v_row.position := rec.position;
                pipe row (v_row);
            end loop;
        end if;
        if v_dam_id is not null then
            for rec in (select id, pedigree_level, position
                from table(animal_pedigree (v_dam_id, p_max_pedigree_level, p_pedigree_level+1, p_position || 'd'))) loop
                                v_row.id := rec.id;
                                v_row.pedigree_level := rec.pedigree_level;
                                v_row.position := rec.position;
                pipe row (v_row);
            end loop;
        end if;
    end if;
    return;
end;

После этого для меня наступает сложная часть: сравнение родословных для поиска совпадающих идентификаторов (а также запоминание глубины, на которой было найдено совпадение) .

В конце концов, я хотел бы вернуть наименьшую глубину, на которой инбридинг был обнаружен, или 0, если его не нашли.

NB! Я хочу сравнивать только две родословные, а не сравнивать идентификаторы внутри одной. (Я хочу, чтобы это игнорировалось, если инбридинг уже имел место, интересует только новый формирующийся инбридинг.)

Для дальнейшей иллюстрации я добавляю 3 примера. Совпадения отмечены * (звездочкой).

Пример 1:

Родословная самца

Depth           1       2       3

                            |--114
                    |--113--|
                    |       |--214
            |--112--|       
            |       |       |--115
            |       |--213--|
            |               |--215
       111--|
            |               |--117
            |       |--116--|
            |       |       |--217
            |--212--|
                    |       |--118
                    |--216--|
                            |--218

Родословная женщины

Depth           1       2       3

                            |--124
                    |--123--|
                    |       |--224
            |--122--|       
            |       |       |--125
            |       |--223--|
            |               |--225
       211--|
            |               |--127
            |       |--126--|
            |       |       |--227
            |--222--|
                    |       |--128
                    |--226--|
                            |--228

[ВОЗВРАТ 0] Нет найдены идентичные ID

Пример 2:

Родословная мужчины

Depth           1       2       3

                            |--114*
                    |--113--|
                    |       |--214
            |--112--|       
            |       |       |--115
            |       |--213--|
            |               |--215
       111--|
            |               |--117
            |       |--116--|
            |       |       |--217
            |--212--|
                    |       |--114*
                    |--216--|
                            |--218

Родословная женщины

Depth           1       2       3

                            |--124
                    |--123--|
                    |       |--224
            |--122--|       
            |       |       |--125
            |       |--223--|
            |               |--225
       211--|
            |               |--127
            |       |--126--|
            |       |       |--227
            |--222--|
                    |       |--128
                    |--226--|
                            |--228

[ВОЗВРАТ 0] Соответствующие ID оба встречаются в мужской родословной. Игнорировать.

Пример 3:

Родословная самца

Depth           1       2       3

                            |--114*
                    |--113--|
                    |       |--214
            |--112--|       
            |       |       |--115
            |       |--213--|
            |               |--215
       111--|
            |               |--117
            |       |--116--|
            |       |       |--217
            |--212--|
                    |       |--118
                    |--216--|
                            |--218

Родословная женщины

Depth           1       2       3

                            |--124
                    |--123--|
                    |       |--224
            |--122--|       
            |       |       |--125
            |       |--223--|
            |               |--225
       211--|
            |               |--127
            |       |--114*-|
            |       |       |--227
            |--222--|
                    |       |--128
                    |--226--|
                            |--228

[ВОЗВРАТ 2] Соответствующие ID-номера найдены на глубине 3 в мужской родословной и на глубине 2 в женской

Ответы [ 2 ]

2 голосов
/ 05 мая 2020

Поскольку вы используете 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;
0 голосов
/ 05 мая 2020

Вы можете использовать рекурсивный CTE для поиска совпадающих предков.

Этот пример не тестировался, поскольку вы не предоставили сценарии для создания образцов данных. В любом случае этот запрос должен работать:

with
l (id, aid, sire_id, dam_id, lvl) as (
  select id, id, sire_id, dam_id, 0 from animal where id = 111 -- male ID
  union all
  select l.id, a.id, l.lvl + 1
  from l
  join animal a on a.id in (l.sire_id, l.dam_id)
),
r (id, aid, sire_id, dam_id, lvl) as (
  select id, id, sire_id, dam_id, 0 from animal where id = 211 -- female ID
  union all
  select r.id, a.id, r.lvl + 1
  from r
  join animal a on a.id in (r.sire_id, r.dam_id)
)
select 
  l.id as male_id, l.aid as male_ancestor_id, l.lvl as male_ancestor_depth,
  r.id as female_id, r.aid as female_ancestor_id, r.lvl as female_ancestor_depth
from l
join r on r.aid = l.aid

Этот запрос возвращает ВСЕ совпадения (их может быть несколько) во всех их комбинациях. Вы можете добавить дополнительные изменения, чтобы удалить повторяющиеся совпадения, поскольку животное может быть предком нескольких уже существующих на каждом дереве.

Кроме того, основной запрос показывает все детали совпадений, включая совпадающих предков и их соответствующие глубины . Вы можете легко изменить его, чтобы отображалась только глубина (как хотите). Или ... вы можете развернуть его, чтобы показать вам «полный путь» до каждого предка. Это зависит от того, какой результат вы предпочитаете. Готов поспорить, как только вы увидите результат, вам понадобится дополнительная информация о нем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...