2012-12-08 4 views
6

Ich möchte eine lineare Abstammungsliste für ein Baumzuchtprojekt erstellen. Die Eltern sind männlich/weiblich-Paare, die nicht (Inzucht) in Beziehung gesetzt werden müssen, damit die Bedeutung dieser Abstammungen zu verfolgen und zu visualisieren ...PostgreSQL rekursiv über 2 Eltern-/Kindtabellen

Im Folgenden finden Sie die Testtabellen/Daten über Postgresql 9.1:

DROP TABLE if exists family CASCADE; 
DROP TABLE if exists plant CASCADE; 

CREATE TABLE family ( 
    id serial, 
    family_key VARCHAR(20) UNIQUE, 
    female_plant_id INTEGER NOT NULL DEFAULT 1, 
    male_plant_id INTEGER NOT NULL DEFAULT 1, 
    filial_n INTEGER NOT NULL DEFAULT -1, -- eg 0,1,2... Which would represent None, F1, F2... 
    CONSTRAINT family_pk PRIMARY KEY (id) 
); 

CREATE TABLE plant ( 
    id serial, 
    plant_key VARCHAR(20) UNIQUE, 
    id_family INTEGER NOT NULL, 
    CONSTRAINT plant_pk PRIMARY KEY (id), 
    CONSTRAINT plant_id_family_fk FOREIGN KEY(id_family) REFERENCES family(id) -- temp may need to remove constraint... 
); 

-- FAMILY Table DATA: 
insert into family (id, family_key, female_plant_id, male_plant_id, filial_n) VALUES (1,'NA',1,1,1); -- Default place holder record 
-- Root level Alba families 
insert into family (id, family_key, female_plant_id, male_plant_id, filial_n) VALUES (2,'family1AA',2,3,1); 
insert into family (id, family_key, female_plant_id, male_plant_id, filial_n) VALUES (3,'family2AA',4,5,1); 
insert into family (id, family_key, female_plant_id, male_plant_id, filial_n) VALUES (4,'family3AA',6,7,1); 
-- F2 Hybrid Families 
insert into family (id, family_key, female_plant_id, male_plant_id, filial_n) VALUES (5,'family4AE',8,11,0); 
insert into family (id, family_key, female_plant_id, male_plant_id, filial_n) VALUES (6,'family5AG',9,12,0); 
insert into family (id, family_key, female_plant_id, male_plant_id, filial_n) VALUES (7,'family6AT',10,13,0); 
-- F3 Double Hybrid family: 
insert into family (id, family_key, female_plant_id, male_plant_id, filial_n) VALUES (9,'family7AEAG',14,15,0); 
-- F3 Tri-hybrid backcross family: 
insert into family (id, family_key, female_plant_id, male_plant_id, filial_n) VALUES (10,'family8AEAGAT',17,16,0); 

-- PLANT Table DATA: 
-- Root level Alba Parents: 
insert into plant (id, plant_key, id_family) VALUES (1,'NA',1);  -- Default place holder record 
insert into plant (id, plant_key, id_family) VALUES (2,'female1A',1); 
insert into plant (id, plant_key, id_family) VALUES (3,'male1A',1); 
insert into plant (id, plant_key, id_family) VALUES (4,'female2A',1); 
insert into plant (id, plant_key, id_family) VALUES (5,'male2A',1); 
insert into plant (id, plant_key, id_family) VALUES (6,'female3A',1); 
insert into plant (id, plant_key, id_family) VALUES (7,'male3A',1); 
-- Female Alba progeny: 
insert into plant (id, plant_key, id_family) VALUES (8,'female4A',2); 
insert into plant (id, plant_key, id_family) VALUES (9,'female5A',3); 
insert into plant (id, plant_key, id_family) VALUES (10,'female6A',4); 
-- Male Aspen Root level parents: 
insert into plant (id, plant_key, id_family) VALUES (11,'male1E',1); 
insert into plant (id, plant_key, id_family) VALUES (12,'male1G',1); 
insert into plant (id, plant_key, id_family) VALUES (13,'female1T',1); 
-- F1 Hybrid progeny: 
insert into plant (id, plant_key, id_family) VALUES (14,'female1AE',5); 
insert into plant (id, plant_key, id_family) VALUES (15,'male1AG',6); 
insert into plant (id, plant_key, id_family) VALUES (16,'male1AT',7); 
-- Hybrid progeny 
insert into plant (id, plant_key, id_family) VALUES (17,'female1AEAG',9); 
-- Tri-hybrid backcross progeny: 
insert into plant (id, plant_key, id_family) VALUES (18,'female1AEAGAT',10); 
insert into plant (id, plant_key, id_family) VALUES (19,'female2AEAGAT',10); 

Unten ist die rekursive Abfrage, die von der I Postgres WITH Queries Dokumentation abgeleitet:

WITH RECURSIVE search_tree(
     family_key 
    , female_plant 
    , male_plant 
    , depth 
    , path 
    , cycle 
) AS (
    SELECT 
      f.family_key 
     , pf.plant_key 
     , pm.plant_key 
     , 1 
     , ARRAY[ROW(pf.plant_key, pm.plant_key)] 
     , false 
    FROM 
      family f 
     , plant pf 
     , plant pm 
    WHERE 
     f.female_plant_id = pf.id 
     AND f.male_plant_id = pm.id 
     AND f.filial_n = 1 -- Include only F1 families (root level) 
     AND f.id <> 1  -- omit the default first family record 

    UNION ALL 

    SELECT 
      f.family_key 
     , pf.plant_key 
     , pm.plant_key 
     , st.depth + 1 
     , path || ROW(pf.plant_key, pm.plant_key) 
     , ROW(pf.plant_key, pm.plant_key) = ANY(path) 
    FROM 
      family f 
     , plant pf 
     , plant pm 
     , search_tree st 
    WHERE 
     f.female_plant_id = pf.id 
     AND f.male_plant_id = pm.id 
     AND f.family_key = st.family_key 
     AND pf.plant_key = st.female_plant 
     AND pm.plant_key = st.male_plant 
     AND f.filial_n <> 1 -- Include only non-F1 families (non-root levels) 
     AND NOT cycle 
) 
SELECT * FROM search_tree; 

Nachfolgend wird der gewünschte Ausgang:

F1 family1AA=(female1A x male1A) > F2 family4AE=(female4A x male1E) > F3 family7AEAG=(female1AE x male1AG) > F4 family8AEAGAT=(female1AEAG x male1AT) 
F1 family2AA=(female2A x male2A) > F2 family5AG=(female5A x male1G) > F3 family7AEAG=(female1AE x male1AG) > F4 family8AEAGAT=(female1AEAG x male1AT) 
F1 family3AA=(female3A x male3A) > F2 family6AT=(female6A x female1T) > F3 family8AEAGAT=(female1AEAG x male1AT) 

Die obige rekursive Abfrage zeigt 3 Zeilen mit den entsprechenden F1-Eltern an, aber der Pfad zeigt die untergeordneten Familien/Eltern nicht an. Ich würde mich freuen zu helfen, die rekursive Ausgabe ähnlich der oben genannten gewünschten Ausgabe zu machen.

+0

Nizza Frage; sehr gut gestellt. sehr vollständig. Ich arbeite daran ... – wildplasser

+0

Ich bin mir nicht sicher, ob ich verstehe, wie die Hierarchie definiert ist. Ich kann keine Eltern/Kind-Beziehung in den Beispieltabellen finden. Kannst du ein bisschen erklären, wie das Elternteil (oder das Kind) gefunden wird? –

+0

Ist es möglich, dass die Zeile mit 'plant.id = 11'' 2' als 'family_id' hat? –

Antwort

4

ich die Abfrage angepasst habe, was ich verstanden habe, nicht unbedingt was :-)

Die Abfrage beginnt bei den drei bestimmten Familien von f.id != 1 AND f.filial_n = 1 definiert, die erforderlich ist, und erweitert rekursiv verfügbar Kinder.

In welchem ​​Zustand nur die letzten drei Treffer ausgewählt werden sollten, ist meines Wissens nicht zu verstehen. Vielleicht für jede Startfamilie die längste Kette von Vorchors?

WITH RECURSIVE expanded_family AS (
    SELECT 
     f.id, 
     f.family_key, 
     pf.id   pd_id, 
     pf.plant_key pf_key, 
     pf.id_family pf_family, 
     pm.id   pm_id, 
     pm.plant_key pm_key, 
     pm.id_family pm_family, 
     f.filial_n 
    FROM family f 
     JOIN plant pf ON f.female_plant_id = pf.id 
     JOIN plant pm ON f.male_plant_id = pm.id 
), 
search_tree AS (
    SELECT 
     f.*, 
     1 depth, 
     ARRAY[f.family_key::text] path 
    FROM expanded_family f 
    WHERE 
     f.id != 1 
     AND f.filial_n = 1 
    UNION ALL 
    SELECT 
     f.*, 
     depth + 1, 
     path || f.family_key::text 
    FROM search_tree st 
     JOIN expanded_family f 
      ON f.pf_family = st.id 
      OR f.pm_family = st.id 
    WHERE 
     f.id <> 1 
) 
SELECT 
    family_key, 
    depth, 
    path 
FROM search_tree; 

Das Ergebnis ist:

family_key | depth |      path      
---------------+-------+------------------------------------------------- 
family1AA  |  1 | {family1AA} 
family2AA  |  1 | {family2AA} 
family3AA  |  1 | {family3AA} 
family4AE  |  2 | {family1AA,family4AE} 
family5AG  |  2 | {family2AA,family5AG} 
family6AT  |  2 | {family3AA,family6AT} 
family7AEAG |  3 | {family1AA,family4AE,family7AEAG} 
family7AEAG |  3 | {family2AA,family5AG,family7AEAG} 
family8AEAGAT |  3 | {family3AA,family6AT,family8AEAGAT} 
family8AEAGAT |  4 | {family1AA,family4AE,family7AEAG,family8AEAGAT} 
family8AEAGAT |  4 | {family2AA,family5AG,family7AEAG,family8AEAGAT} 

Technisches Material:

  • ich die cycle Sachen entfernt haben, weil für saubere Daten, die es nicht nötig sein sollte (IMHO).

  • expanded_family kann inline werden, wenn ein merkwürdiges Leistungsproblem auftritt, aber für jetzt macht es die rekursive Abfrage lesbarer.

EDIT

Eine leichte Modifikation der Abfrage können diese Zeilen auswählen, in denen für jede „root“ Familie (das heißt diejenigen, für die die Abfrage gestartet wird), besteht die längste Pfad.

zeige ich nur den geänderten Teil in search_tree, so dass Sie den Kopf aus dem vorherigen Abschnitt kopieren:

-- ... 
search_tree AS 
(
    SELECT 
     f.*, 
     f.id   family_root, -- remember where the row came from. 
     1 depth, 
     ARRAY[f.family_key::text] path 
    FROM expanded_family f 
    WHERE 
     f.id != 1 
     AND f.filial_n = 1 
    UNION ALL 
    SELECT 
     f.*, 
     st.family_root, -- propagate the anchestor 
     depth + 1, 
     path || f.family_key::text 
    FROM search_tree st 
     JOIN expanded_family f 
      ON f.pf_family = st.id 
      OR f.pm_family = st.id 
    WHERE 
     f.id <> 1 
) 
SELECT 
    family_key, 
    path 
FROM 
(
    SELECT 
     rank() over (partition by family_root order by depth desc), 
     family_root, 
     family_key, 
     depth, 
     path 
    FROM search_tree 
) AS ranked 
WHERE rank = 1; 

Das Ergebnis ist:

family_key |      path      
---------------+------------------------------------------------- 
family8AEAGAT | {family1AA,family4AE,family7AEAG,family8AEAGAT} 
family8AEAGAT | {family2AA,family5AG,family7AEAG,family8AEAGAT} 
family8AEAGAT | {family3AA,family6AT,family8AEAGAT} 
(3 rows) 

EDIT2

Basierend auf den Kommentaren fügte ich eine pretty_print Version des Pfades hinzu:

WITH RECURSIVE expanded_family AS (
    SELECT 
     f.id, 
     pf.id_family pf_family, 
     pm.id_family pm_family, 
     f.filial_n, 
     f.family_key || '=(' || pf.plant_key || ' x ' || pm.plant_key || ')' pretty_print 
    FROM family f 
     JOIN plant pf ON f.female_plant_id = pf.id 
     JOIN plant pm ON f.male_plant_id = pm.id 
), 
search_tree AS 
(
    SELECT 
     f.id, 
     f.id   family_root, 
     1 depth, 
     'F1 ' || f.pretty_print path 
    FROM expanded_family f 
    WHERE 
     f.id != 1 
     AND f.filial_n = 1 
    UNION ALL 
    SELECT 
     f.id, 
     st.family_root, 
     st.depth + 1, 
     st.path || ' -> F' || st.depth+1 || ' ' || f.pretty_print 
    FROM search_tree st 
     JOIN expanded_family f 
      ON f.pf_family = st.id 
      OR f.pm_family = st.id 
    WHERE 
     f.id <> 1 
) 
SELECT 
    path 
FROM 
(
    SELECT 
     rank() over (partition by family_root order by depth desc), 
     path 
    FROM search_tree 
) AS ranked 
WHERE rank = 1; 

Das Ergebnis ist

path                   
---------------------------------------------------------------------------------------------------------------------------------------------------------- 
F1 family1AA=(female1A x male1A) -> F2 family4AE=(female4A x male1E) -> F3 family7AEAG=(female1AE x male1AG) -> F4 family8AEAGAT=(female1AEAG x male1AT) 
F1 family2AA=(female2A x male2A) -> F2 family5AG=(female5A x male1G) -> F3 family7AEAG=(female1AE x male1AG) -> F4 family8AEAGAT=(female1AEAG x male1AT) 
F1 family3AA=(female3A x male3A) -> F2 family6AT=(female6A x female1T) -> F3 family8AEAGAT=(female1AEAG x male1AT) 
(3 rows) 
+0

Super - ich sollte es von hier aus nehmen können! Ich könnte PL/pgsql verwenden, um die doppelten Vorfahren zu entfernen und die Eltern/Kind-Formatierung hinzuzufügen. Danke für Ihre Hilfe! Du hast geholfen, bessere Bäume zu züchten !!! – user1888167

+0

@ user1888167: pl/pgsql ist nicht erforderlich. Sie können geeignete Filter an drei Stellen hinzufügen: Das 'WHERE' des nicht-rekursiven Teils (wobei' f.id' und 'f.filial_id' bereits markiert sind), das rekursive' WHERE' und Sie können auch einen Filter hinzufügen die "äußere" wählen. Das "äußere" 'SELECT' ist der übliche Ort für sowas. Um die Filterung durchzuführen, können Sie mehr Informationen verwenden, als die aktuelle Ausgabe zeigt.
Ich wusste nur nicht, welche Kriterien Sie anwenden möchten. –

+0

Das wünschenswerteste Kriterium wäre, nur die "vollständigen Familienplattformen" anzuzeigen, die die letzten drei Zeilen Ihrer Ausgabe darstellen würden. Also ja, für jede Startfamilie wäre es die längste einzigartige Kette von Vorgängern? Ist das möglich? – user1888167