Вычисление агрегированных значений в иерархической структуре снизу вверх - PullRequest
2 голосов
/ 15 декабря 2009

Мне нужно пересчитать количества в структуре спецификации, которая гарантирует только правильные значения количества для конечных узлов.

Если вышесказанное немного размыто, не волнуйтесь, ниже приведен упрощенный пример.

Рассмотрим иерархическую таблицу, определяющую три столбца: ID (PK), PID (родительский идентификатор в иерархии) и Q (количество - чего-либо, для текущей записи).

set nocount on
declare @results table(
        ID  nvarchar(3), 
        PID nvarchar(3), 
        Q   int,
        lvl int,
        ord int
    )

Столбец ord служит только для сортировки результатов.

Столбец lvl определяет уровень для текущей записи в иерархии.

Для тех, кому интересно, как эти столбцы поддерживаются, таблица @results заполняется в реальном мире функцией, которая выполняет все трюки; для этого примера им будут заданы жестко закодированные значения.

Теперь проблема в том, что правильные значения Q гарантируются только для узлов конечного уровня в иерархии. Для других узлов Q может быть или не быть правильно определено. Моя задача - пересчитать значения Q для этих узлов.

Пример данных:

insert into @results(ord, lvl, ID, PID, Q)
select 1, 0, 'A' as ID, null as PID, null as Q union
select 2, 1, 'B'  , 'A' , 15 union
select 3, 1, 'C'  , 'A' , 10 union
select 4, 2, 'B1' , 'B' , 6  union
select 5, 2, 'B2' , 'B' , 4  union
select 6, 2, 'C1' , 'C' , 5  union
select 7, 2, 'C2' , 'C' , 3  union
select 8, 3, 'C11', 'C1', 4  union
select 9, 3, 'C12', 'C1', 3

Как видите, величины для B и C1 неверны: они 15 и 5, но должны быть 10 и 7:

select * from @results order by ord

Вот исходные данные:

ID   PID            Q         lvl         ord
---- ---- ----------- ----------- -----------
A    NULL        NULL           0           1
B    A             15           1           2
C    A             10           1           3
B1   B              6           2           4
B2   B              4           2           5
C1   C              5           2           6
C2   C              3           2           7
C11  C1             4           3           8
C12  C1             3           3           9

Наконец, вопрос: есть ли способ обновить эту таблицу на основе набора, чтобы все количества обновлялись суммированными количествами дочерних узлов снизу вверх?

Лучшее, с чем я пришел, можно увидеть ниже, но оно не основано на:

declare @level int

select @level = max(lvl) from @results

while (@level > 0)
begin
    update r set Q = s.SumQ
    from @results r inner join (
            select PID, sum(Q) as SumQ 
            from   @results 
            where lvl = @level group by PID
        ) s on ( r.ID = s.PID )

    set @level = @level - 1
end

select * from @results

, что дает правильные количества:

ID   PID            Q         lvl         ord
---- ---- ----------- ----------- -----------
A    NULL          20           0           1
B    A             10           1           2
C    A             10           1           3
B1   B              6           2           4
B2   B              4           2           5
C1   C              7           2           6
C2   C              3           2           7
C11  C1             4           3           8
C12  C1             3           3           9

Спасибо!

1 Ответ

2 голосов
/ 15 декабря 2009
;WITH   q AS
        (
        SELECT  *, id AS initial
        FROM    @results
        UNION ALL
        SELECT  r.*, initial
        FROM    q
        JOIN    @results r
        ON      r.pid = q.id
        )
UPDATE  ru
SET     q = qn.nq
FROM    @results ru
JOIN    (
        SELECT  initial,
                SUM(rq) AS nq
        FROM    q
        LEFT JOIN
                (
                SELECT  id,
                        CASE
                        WHEN EXISTS
                        (
                        SELECT  NULL
                        FROM    @results ri
                        WHERE   ri.pid = r.id
                        )
                        THEN NULL
                        ELSE q
                        END AS rq
                FROM    @results r
                ) r
        ON      r.id = q.id
                AND r.id <> q.initial
        GROUP BY
                q.initial
        ) qn
ON      ru.id = qn.initial
        AND qn.nq IS NOT NULL
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...