2010-08-21 18 views
8

Ich habe eine Tabelle, die eine Hierarchie definiert:SQL Query: Hierarchical Coalesce

Create Table [example] (
    id   Integer Not Null Primary Key, 
    parentID Integer  Null, 
    largeData1 nVarChar(max) Null, 
    largeData2 nVarChar(max) Null); 
    -- largeData3...n also exist 

Insert Into [example] (id, parentID, largeData1, largeData2) 
Select 1, null, 'blah blah blah', null   Union 
Select 2, 1, null,    null   Union 
Select 3, 1, 'foo bar foobar', null   Union 
Select 4, 3, null,    'lorem ipsum' Union 
Select 5, 4, null,    null; 

Hierarchiediagramm für diese Daten:

Hierarchy diagram

Ich möchte schreiben, um eine Abfrage, die eine einzelne zurück Zeile für jeden gegebenen [ID] Wert. Die Zeile sollte die [id] und [parentID] Informationen dieser Zeile enthalten. Es sollte auch die Felder [largeData1 ... n] enthalten. Wenn ein largeData-Feld jedoch null ist, sollte es die Hierarchie durchlaufen, bis ein Nicht-Null-Wert für dieses Feld gefunden wird. Kurz gesagt sollte es wie die Coalesce-Funktion funktionieren, außer über eine Hierarchie von Zeilen statt einer Reihe von Spalten.

Beispiel:

Wo [id] = 1:

id:   1 
parentID: null 
largeData1: blah blah blah 
largeData2: null 

Wo [id] = 2

id:   1 
parentID: 1 
largeData1: blah blah blah 
largeData2: null 

Wo [id] = 3

id:   3 
parentID: 1 
largeData1: foo bar foobar 
largeData2: null 

Wo [id] = 4

id:   4 
parentID: 3 
largeData1: foo bar foobar 
largeData2: lorem ipsum 

Wo [id] = 5

id:   5 
parentID: 4 
largeData1: foo bar foobar 
largeData2: lorem ipsum 

Bisher habe ich diese:

Declare @id Integer; Set @id = 5; 

With heirarchy 
    (id, parentID, largeData1, largeData2, [level]) 
As (
    Select id, parentID, largeData1, 
      largeData2, 1 As [level] 
    From example 
    Where id = @id 

    Union All 

    Select parent.id, parent.parentID, 
      parent.largeData1, 
      parent.largeData2, 
      child.[level] + 1 As [level] 
    From example As parent 
    Inner Join heirarchy As child 
     On parent.id = child.parentID) 

Select id, parentID, 
    (Select top 1 largeData1 
    From heirarchy 
    Where largeData1 Is Not Null 
    Order By [level] Asc) As largeData1, 

    (Select top 1 largeData2 
    From heirarchy 
    Where largeData2 Is Not Null 
    Order By [level] Asc) As largeData2 

From example 
Where [id] = @id; 

Diese gibt die Ergebnisse zurück, nach denen ich suche. Gemäß dem Abfrageplan wird jedoch für jedes largeData-Feld, das ich zurückziehe, ein separater Durchlauf durch die Hierarchie durchgeführt.

Wie kann ich das effizienter machen?

Dies ist offensichtlich eine vereinfachte Version eines komplexeren Problems. Die letzte Abfrage gibt Daten im XML-Format zurück, sodass alle Lösungen, die die FOR XML-Klausel enthalten, vollkommen in Ordnung sind.

Ich kann eine CLR-Aggregatfunktion dafür erstellen, wenn dies helfen würde. Ich habe diese Route noch nicht erkundet.

Antwort

6

Ich kam mit dieser:

DECLARE @Id int 

SET @Id = 5 


;WITH cte (Id, ParentId, SaveParentId, LargeData1, LargeData2) 
as (-- The "anchor", your target Id 
    select 
     ex.Id 
     ,ex.ParentId 
     ,ex.ParentId SaveParentId -- Not changed throughout the CTE 
     ,ex.LargeData1 
     ,ex.LargeData2 
     from Example ex 
     where ex.Id = @Id 
    union all select 
       cte.Id 
       ,ex.ParentId 
       ,cte.SaveParentId -- Not changed throughout the CTE 
       -- These next are only "reset" if they are null and a not-null 
       -- value was found at this level 
       ,isnull(ex.LargeData1, cte.LargeData2) 
       ,isnull(ex.LargeData2, cte.LargeData2) 
     from Example ex 
     inner join cte 
     on cte.ParentId = ex.Id) 
select 
    Id 
    ,SaveParentId  ParentId 
    ,max(LargeData1) LargeData1 
    ,max(LargeData2) LargeData2 
from cte 
group by Id, SaveParentId 

Grundsätzlich an Ihrem Zielknoten starten und den Baum hinauf, Ihre null Spalten mit nicht-Null-Werte zu ersetzen, falls und wenn sie gefunden werden.

(Sorry, aber ich mache kein XML am Wochenende.)

+0

+1 für das Hochdrücken von Nicht-Null-Werten. Aber die Verwendung von MAX kann problematisch sein. Wenn in Zeile 3 der Beispieldaten "acoo bar bar" anstelle von "foo bar bar" steht, gibt die Abfrage für @ id = 5 "blah blabla" für largeData1 zurück. – 8kb

+1

Wenn der Wert einer Spalte bei einem gegebenen Wert null ist, wird er beim Ersetzen des CTE durch den Wert auf dieser Ebene ersetzt, andernfalls bleibt er unverändert. Eine Zeile wird pro Level produziert. Wenn die cte fertig ist, ist der Wert für eine Spalte über alle Zeilen entweder Null oder der erste gefundene Wert. Aggregationen ignorieren Nullen und lassen nur den einen Wert für max (oder min) übrig. –