2010-08-21 13 views
8

tengo una tabla que define una jerarquía:de consultas SQL: jerárquica 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; 

diagrama de jerarquía para estos datos:

Hierarchy diagram

Quiero escribir una consulta que devolverá un único fila para cualquier valor [id] dado. La fila debe contener la información de esa fila [id] y [parentID]. También debe contener los campos [largeData1 ... n]. Sin embargo, si un campo largeData es nulo, debe recorrer la jerarquía hasta que se encuentre un valor no nulo para ese campo. En resumen, debe funcionar como la función de fusión, excepto a través de una jerarquía de filas en lugar de un conjunto de columnas.

Ejemplo:

Donde [id] = 1:

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

Donde [id] = 2

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

Donde [id] = 3

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

Donde [id] = 4

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

Donde [id] = 5

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

Hasta ahora, tengo esto:

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; 

Este devuelve los resultados que estoy buscando. Sin embargo, de acuerdo con el plan de consulta, está haciendo un pase separado a través de la jerarquía para cada campo de datos grandes que retiro.

¿Cómo puedo hacer esto más eficiente?

Esta es obviamente una versión simplificada de un problema más complejo. La consulta final devolverá los datos en formato XML, por lo que cualquier solución que implique la cláusula FOR XML está perfectamente bien.

Puedo crear una función de agregado CLR para esto, si hacerlo ayudaría. Aún no exploré esa ruta.

Respuesta

6

me ocurrió esto:

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 

Básicamente, comience en el nodo de destino y caminar hasta el árbol, la sustitución de las columnas con los valores nulos no nulo si y cuando se encuentran.

(Lo siento, pero no hago XML los fines de semana)

+0

+1 para aumentar los valores no nulos. Pero usar MAX puede ser problemático. Si la fila 3 de los datos de muestra dice "afoo bar bar" en lugar de "foo bar bar", la consulta para @ id = 5 devolverá "blah blah blah" para largeData1. – 8kb

+1

Cuando "sube" dentro del CTE, si en un nivel determinado el valor de una columna es nulo, se reemplaza con el valor en ese nivel; de lo contrario, no se modifica. Se produce una fila por nivel. Por lo tanto, cuando se completa el cte, el valor de una columna en todas las filas será nulo o el primer valor encontrado. Las agregaciones ignoran los valores nulos, dejando solo un valor para max (o min) para seleccionar. –