2016-09-06 5 views
0

Ich habe eine Datenstruktur, wo verschiedene Datenelemente miteinander verwandt werden können. Ein Datensatz kann der Vater vieler untergeordneter Datensätze sein, aber ein untergeordneter Datensatz kann nur einen übergeordneten Datensatz haben. Ich arbeite an einer Abfrage, um diese Datensatz "Familien" zu überprüfen, ob sie alle als veraltet markiert sind, so dass ich sie löschen kann.CTE-Abfrage vereinfachen

Um dies zu lösen, schrieb ich eine CTE, die am untersten Kind Datensätze beginnt, und arbeitet den Baum, die Aufnahme des Status, um mit einem Familienstatus auf der obersten Ebene zu beenden.

Ein Problem, auf das ich mit dieser Methode stieß war, wenn ein Datensatz mehrere Kinder hat, endete ich mit mehreren Datensätzen für die oberste Ebene Eltern. In einigen Fällen könnte ein untergeordneter Pfad für die Löschung geeignet sein, der andere jedoch nicht. Ich konnte keine Möglichkeit finden, dies im CTE zu identifizieren, und endete mit einer Unterabfrage, wo ich die Anzahl der Pfade, die gelöscht werden können, zähle und nicht, dann nur Datensätze mit> 1 'delete' Pfaden und 0 zurücksende 'nicht löschen' Pfade.

Hier ist mein Code, komplett mit Beispieldaten

create table #demand (id int, [status] varchar(10)) 
create table #relation (parent int, child int) 

insert into #demand values (1, 'active') 
insert into #demand values (2, 'obsolete') 
insert into #demand values (3, 'obsolete') 
insert into #demand values (4, 'obsolete') 
insert into #demand values (5, 'active') 
insert into #demand values (6, 'obsolete') 

insert into #relation values(2, 3) 
insert into #relation values(2, 4) 
insert into #relation values(4, 5) 

--the CTE splitFamily traverses the split family records from bottom up, checking that each record is Obsolete. If any record is not 
;with 
splitFamily as (
    select 
     D.id, 
     DR.child, 
     case when D.[status] = 'obsolete' then 'Y' else 'N' end as CanDelete 
    from #demand D 
    left outer join #relation DR on D.id = DR.parent 
    where DR.child is null 

    union all 

    select 
     D.id, 
     DR.child, 
     case when D.[status] = 'obsolete' and splitFamily.CanDelete = 'Y' then 'Y' else 'N' end as CanDelete 
    from 
     splitFamily 
    join #relation DR on splitFamily.id = DR.child 
    join #demand D on DR.parent = D.id 
) 
select id from (
    select parentLevel.id, 
    sum(case when parentLevel.CanDelete = 'Y' then 1 else 0 end) as "Y", 
    sum(case when parentLevel.CanDelete = 'N' then 1 else 0 end) as "N" 
    from splitFamily parentLevel 
    --The following join ensures we only return top level parent records to be deleted. 
    left join splitFamily children on parentLevel.id = children.child 
    where children.id is null 
    group by parentLevel.id 
) as splits where Y > 0 and N = 0 

drop table #demand 
drop table #relation 

Dieser Code funktioniert und gibt Rekord 6 als nur geeignet Datensatz löschen. Wenn Datensatz 5 in 'veraltet' geändert wird, enthält die Abfrage in den Ergebnissen korrekt 2.

Meine Frage ist, ob es eine sauberere, klarere Möglichkeit gibt, diese geteilten Pfade innerhalb des CTE zu identifizieren, um die zusätzliche Unterabfrage und das Zählen der Pfade zu vermeiden. Für mich verschleiert dies den Zweck des Codes und macht es wesentlich schwieriger, ihn aufrechtzuerhalten.

+0

also wann können Sie einen Knoten löschen? Ist es, wenn es veraltet ist und keines seiner Kinder nicht veraltet ist, keines der Kinderkinder veraltet usw.? Ist das die Regel? – Cato

+0

@AndrewDeighton richtig. Der Knoten auf der obersten Ebene und alle untergeordneten Knoten müssen sich in einem veralteten Status befinden. Dann möchte ich die ID des obersten Knotens zurückgeben. (Meine Löschprozedur wird zurückgesetzt, um die gesamte Familie innerhalb einer einzigen Transaktion zu löschen.) –

+0

Wenn Sie also beim Löschen Ihrer Hierarchie einen Löschvorgang gefunden haben, möchten Sie den Vorgang stoppen, damit Sie diesen Knoten melden können. Wie ... an diesem Punkt interessieren Sie sich nicht für die Nachkommen, richtig? – JNevill

Antwort

0

Ich änderte schließlich meine endgültige Auswahl zu den folgenden. Mit Hilfe von Fensterfunktionen konnte ich die verwirrenden Summierungen vermeiden, und meiner Meinung nach ist die Auswahl jetzt viel klarer. Die FIRST_VALUE-Funktion stellt sicher, dass "N" zurückgegeben wird, wenn alle Zeilen N für jede id hatten.

select distinct id from (
    select parentLevel.id, 
    FIRST_VALUE(parentLevel.CanDelete) over(partition by parentLevel.DemandId order by parentLevel.CanDelete) as CanDelete 
    from splitFamily parentLevel 
    left join splitFamily children on parentLevel.id = children.child 
    where children.id is null 
) as splits where CanDelete = 'Y'