2008-09-18 14 views
7

Je dois additionner des points sur chaque niveau gagné par un arbre d'utilisateurs. Le niveau 1 est la somme des points utilisateurs du niveau utilisateurs 1 sous l'utilisateur. Niveau 2 est le niveau 1 points des utilisateurs 2 niveaux en dessous de l'utilisateur, etc ...Comment calculer la somme des valeurs dans une arborescence en utilisant SQL

Le calcul se fait une fois par mois sur un serveur non-production, pas de soucis de performance.

À quoi ressemblerait le SQL pour le faire?

Si vous êtes confus, ne vous inquiétez pas, je le suis aussi!

table utilisateur:

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 

sortie doit être:

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

Syntaxe SQL Server et les fonctions de préférence ...

Répondre

1

Je dirais: créer une procédure stockée, a probablement Meilleure performance. Ou si vous avez un nombre maximum de niveaux, vous pouvez créer des sous-requêtes, mais elles auront une performance très médiocre.

(Ou vous pourriez obtenir MS SQL Server 2008 et obtenir les nouvelles fonctions de la hiérarchie ...;))

+0

Je pense aussi, mais à quoi ressemblerait la procédure? – Jrgns

2

Si vous utilisez un SGBD Oracle qui serait assez simple car Oracle prend en charge les requêtes d'arbres avec le CONNECT BY/COMMENCE avec la syntaxe. Pour SQL Server, je pense que vous pourriez trouver Common Table Expressions utile

2

Les arbres ne fonctionnent pas bien avec SQL. Si vous avez très (très très) peu d'accès en écriture, vous pouvez changer l'implémentation de l'arborescence pour utiliser des ensembles imbriqués, ce qui rendrait cette requête incroyablement facile.

Exemple (si je ne me trompe pas):

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

Cependant, tout changement sur l'arbre exigent de toucher une énorme quantité de lignes. Il vaut probablement mieux faire la récursion dans votre client.

0

Vous avez deux options:

  1. Utilisez un curseur et un appel de fonction récursive définie par l'utilisateur (il est assez lent)
  2. Créer une table de cache, mettez à jour sur INSERT en utilisant un déclencheur (il est la solution la plus rapide, mais pourrait être problématique si vous avez beaucoup de mises à jour de la table principale)
  3. Faites un calcul récursif côté client (préférable si vous n'avez pas trop d'enregistrements)
1

Si vous Je travaille avec des arbres stockés dans une base de données relationnelle, je suggère de regarder "ensemble imbriqué" ou "traversée de l'arbre de pré-modification modifié". Le SQL sera aussi simple que cela:

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

... et cela pour chaque nœud que vous êtes intéressé par

Peut-être que cela vous aidera. http://www.dbazine.com/oracle/or-articles/tropashko4 ou utiliser Google.

0

Vous pouvez écrire une fonction récursive simple pour faire le travail. Mon MSSQL est un peu rouillé, mais il ressemblerait à ceci:

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

Ok, à quoi ressemblera la fonction? – Jrgns

+0

Je n'ai pas d'instalation MSSQL ici, mais cela ressemblerait à ceci: getsum (parentNode int) sum = sélectionne la valeur où node = parentNode; foreach row dans les enfants sélectionnés de la table où parent = parentNode sum = sum + getsum (childnode) Vous l'appelez dans le nœud supérieur. –

1

SQL en général, comme d'autres ont dit, ne gère pas bien ces relations. En règle générale, une table «relations de substitution est nécessaire (id, id_parent, clé unique sur (id, id_parent)), où:

  • chaque fois que vous ajoutez un enregistrement dans 'table', vous:

    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];

  • ont une logique pour éviter les cycles

  • faire en sorte que les mises à jour, les suppressions sur les 'relations' sont traitées avec des procédures stockées

Étant donné cette table, vous voulez:

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

Ok, cela vous donne les résultats que vous recherchez, mais il n'y a aucune garantie que je n'ai pas manqué quelque chose. Considérez cela comme un point de départ. Je SQL 2005 pour le faire, SQL 2000 ne prend pas en charge

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

de CTE Le tableau suivant:

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

Et le tableau montant suivant:

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

Seules les feuilles (dernière niveau) Les ID ont une valeur définie. La requête SQL pour obtenir les données ressemble à:

;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 

cela se traduit par la sortie:

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

J'espère que cela aide.