2008-09-18 7 views
7

Мне нужно суммировать очки на каждом уровне, заработанные деревом пользователей. Уровень 1 - это сумма очков пользователей уровня пользователя 1 ниже пользователя. Уровень 2 - это уровни уровня 1 пользователей на уровне 2 ниже пользователя и т. Д.Как вычислить сумму значений в дереве с помощью SQL

Расчет происходит один раз в месяц на не-производственном сервере, не заботясь о производительности.

Что бы SQL выглядел, как это сделать?

Если вы в замешательстве, не волнуйтесь, я тоже!

Пользователь таблица:

ID ParentID Points 
1  0   230 
2  1   150 
3  0   80 
4  1   110 
5  4   54 
6  4   342 

Tree: 
0 
|---\ 
1 3 
| \ 
2 4--- 
    \ \ 
    5 6 

Вывод должен быть:

ID Points Level1  Level2 
1  230  150+110 150+110+54+342 
2  150 
3  80 
4  110  54+342 
5  54 
6  342 

SQL Server Синтаксис и функции предпочтительно ...

ответ

1

Я бы сказал: создать хранимую процедуру, вероятно, имеет лучшее представление. Или, если у вас есть максимальное количество уровней, вы можете создавать подзапросы, но они будут иметь очень высокую производительность.

(Или вы можете получить MS SQL Server 2008 и получить новые функции иерархии ...;))

+0

Мое мышление также, но как выглядит процедура? – Jrgns 2008-09-18 10:10:54

2

Если вы используете СУБД Oracle, которые были бы довольно просто, так как Oracle поддерживает дерево запросов с CONNECT BY/STARTS WITH синтаксис. Для SQL Server я думаю, вы можете найти Common Table Expressions

2

Деревья не работают с SQL. Если у вас есть очень (очень очень) число обращений к записи, вы можете изменить реализацию дерева для использования вложенных множеств, что сделало бы этот запрос невероятно простым.

Пример (если я не ошибаюсь):

SELECT SUM(points) 
FROM users 
where left > x and right < y 

Однако любые изменения на дереве требует прикосновения огромное количество строк. Вероятно, лучше всего сделать рекурсию в вашем клиенте.

0

У вас есть несколько вариантов:

  1. Используйте курсор и вызов функции рекурсивного определяемые пользователем (это довольно медленно)
  2. Создание таблицы кэша, обновлять его на INSERT с помощью триггера (это самое быстрое решение, но может быть проблематичным, если у вас есть большое количество обновлений в основную таблицу)
  3. ли рекурсивное вычисление на сторону клиента (желательно, если вы не слишком много записей)
1

Если вы работают с деревьями, хранящимися в реляционной базе данных, я бы предложил посмотреть «вложенный набор» или «измененный обход дерева предзаказов». SQL будет так же просто, как:

SELECT id, 
     SUM(value) AS value 
FROM table 
WHERE left>left\_value\_of\_your\_node 
    AND right<$right\_value\_of\_your\_node; 

... и сделать это для каждого узла вы заинтересованы в

Может быть, это поможет вам:. http://www.dbazine.com/oracle/or-articles/tropashko4 или используйте google.com.

0

Вы можете написать простую рекурсивную функцию для выполнения задания. Мой MSSQL немного ржавый, но это будет выглядеть следующим образом:

CREATE FUNCTION CALC 
(
@node integer, 
) 
returns 
(
@total integer 
) 
as 
begin 
    select @total = (select node_value from yourtable where node_id = @node); 

    declare @children table (value integer); 
    insert into @children 
    select calc(node_id) from yourtable where parent_id = @node; 

    @current = @current + select sum(value) from @children; 
    return 
end 
+0

Хорошо, как будет выглядеть функция? – Jrgns 2008-09-18 10:25:20

1

SQL в общем, как и другие говорили, не обрабатывает хорошо такие отношения. Как правило, таблица суррогатных 'отношения' необходимо (идентификатор, parent_id, уникальный ключ на (ид, parent_id)), где:

  • каждый раз, когда вы добавляете запись в 'таблице', ты:

    INSERT INTO relations (id, parent_id) VALUES ([current_id], [current_id]);

    INSERT INTO relations (id, parent_id) VALUES ([current_id], [current_parent_id]);

    INSERT INTO relations (id, parent_id) SELECT [current_id], parent_id FROM relations WHERE id = [current_parent_id];

  • имеет логику, чтобы избежать циклов

  • убедитесь, что обновления, делеции на 'отношений' обрабатываются с помощью хранимых процедур

Учитывая, что стол, вы хотите:

SELECT rel.parent_id, SUM(tbl.points) 
FROM table tbl INNER JOIN relations rel ON tbl.id=rel.id 
WHERE rel.parent_id <> 0 
GROUP BY rel.parent_id; 
1

Хорошо, это дает вам результаты, которые вы ищете, но нет никаких гарантий, что я ничего не пропустил. Считайте это отправной точкой. Я использовал SQL 2005, чтобы сделать это, SQL 2000 не поддерживает CTE в

WITH Parent (id, GrandParentId, parentId, Points, Level1Points, Level2Points) 
AS 
(
    -- Find root 
    SELECT id, 
      0 AS GrandParentId, 
      ParentId, 
      Points, 
      0 AS Level1Points, 
      0 AS Level2Points 
    FROM tblPoints ptr 
    WHERE ptr.ParentId = 0 

    UNION ALL (
    -- Level2 Points 
    SELECT pa.GrandParentId AS Id, 
      NULL AS GrandParentId, 
      NULL AS ParentId, 
      0 AS Points, 
      0 AS Level1Points, 
      pa.Points AS Level2Points 
    FROM tblPoints pt 
      JOIN Parent pa ON pa.GrandParentId = pt.Id 
    UNION ALL 
    -- Level1 Points 
    SELECT pt.ParentId AS Id, 
      NULL AS GrandParentId, 
      NULL AS ParentId, 
      0 AS Points, 
      pt.Points AS Level1Points, 
      0 AS Level2Points 
    FROM tblPoints pt 
      JOIN Parent pa ON pa.Id = pt.ParentId AND pa.ParentId IS NOT NULL 
    UNION ALL 
    -- Points 
    SELECT pt.id, 
      pa.ParentId AS GrandParentId, 
      pt.ParentId, 
      pt.Points, 
      0 AS Level1Points, 
      0 AS Level2Points 
    FROM tblPoints pt 
      JOIN Parent pa ON pa.Id = pt.ParentId AND pa.ParentId IS NOT NULL) 
) 
SELECT id, 
    SUM(Points) AS Points, 
    SUM(Level1Points) AS Level1Points, 
    CASE WHEN SUM(Level2Points) > 0 THEN SUM(Level1Points) + SUM(Level2Points) ELSE 0 END AS Level2Points 
FROM Parent 
GROUP BY id 
ORDER by id 
0

В следующей таблице:

Id ParentId 
1 NULL 
11 1 
12 1 
110 11 
111 11 
112 11 
120 12 
121 12 
122 12 
123 12 
124 12 

И следующая сумма таблица:

Id  Val 
110 500 
111 50 
112 5 
120 3000 
121 30000 
122 300000 

только листья (последний level) У Id есть определенное значение. SQL-запрос, чтобы получить данные, выглядит следующим образом:

;WITH Data (Id, Val) AS 
(
    select t.Id, SUM(v.val) as Val from dbo.TestTable t 
    join dbo.Amount v on t.Id = v.Id 
    group by t.Id 
) 

select cd.Id, ISNULL(SUM(cd.Val), 0) as Amount FROM 
(
    -- level 3 
    select t.Id, d.val from TestTable t 
    left join Data d on d.id = t.Id 

    UNION 

    -- level 2 
    select t.parentId as Id, sum(y.Val) from TestTable t 
    left join Data y on y.id = t.Id 
    where t.parentId is not null 
    group by t.parentId 

    UNION 

    -- level 1 
    select t.parentId as Id, sum(y.Val) from TestTable t 
    join TestTable c on c.parentId = t.Id 
    left join Data y on y.id = c.Id 
    where t.parentId is not null 
    group by t.parentId 
) AS cd 
group by id 

это приводит к выходу:

Id  Amount 
1  333555 
11 555 
12 333000 
110 500 
111 50 
112 5 
120 3000 
121 30000 
122 300000 
123 0 
124 0 

Я надеюсь, что это помогает.

Смежные вопросы