2010-08-21 39 views
8

J'ai une table qui définit une hiérarchie:requête SQL: hiérarchique 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; 

diagramme Hiérarchie pour ces données:

Hierarchy diagram

Je veux écrire une requête qui renverra un seul row pour toute valeur [id] donnée. La ligne doit contenir les informations [id] et [parentID] de cette ligne. Il devrait également contenir les champs [largeData1 ... n]. Toutefois, si un champ largeData est null, il doit traverser la hiérarchie jusqu'à ce qu'une valeur non nulle pour ce champ soit rencontrée. Il devrait, en bref, fonctionner comme la fonction coalesce, sauf à travers une hiérarchie de lignes au lieu d'un ensemble de colonnes.

Exemple:

où [id] = 1:

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

où [id] = 2

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

où [id] = 3

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

Où [id] = 4

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

Où [id] = 5

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

Jusqu'à présent, j'ai ceci:

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; 

Cette renvoie les résultats que je recherche. Cependant, selon le plan de requête, il effectue un passage séparé à travers la hiérarchie pour chaque champ largeData que je recule.

Comment rendre cela plus efficace?

Ceci est évidemment une version simplifiée d'un problème plus complexe. La requête finale retournera les données au format XML, de sorte que toutes les solutions impliquant la clause FOR XML sont parfaitement bien.

Je peux créer une fonction d'agrégation CLR pour cela, si cela peut aider. Je n'ai pas encore exploré cette route.

Répondre

6

je suis venu avec ceci:

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 

Fondamentalement, commencer à votre noeud cible et marcher l'arbre, en remplaçant vos colonnes null avec des valeurs non nulles si et quand ils se trouvent.

(Désolé, mais je ne fais pas XML le week-end.)

+0

+1 pour faire monter des valeurs non nulles. Mais l'utilisation de MAX peut être problématique. Si la ligne 3 de l'exemple de données indique "afoo bar bar" au lieu de "foo bar bar", la requête pour @ id = 5 retournera "bla bla bla" pour largeData1. – 8kb

+1

Lorsque "remonter" dans le CTE, si à un niveau donné la valeur d'une colonne est nulle, elle est remplacée par la valeur à ce niveau, sinon elle est laissée inchangée. Une ligne est produite par niveau. Ainsi, lorsque le cte est terminé, la valeur d'une colonne sur toutes les lignes sera soit nulle soit la première valeur rencontrée. Les agrégations ignorent les valeurs nulles, laissant uniquement la valeur de max (ou min) à sélectionner. –