2008-09-18 5 views
7

Ich muss Punkte auf jeder Ebene summieren von einem Baum von Benutzern verdient. Stufe 1 ist die Summe der Benutzerpunkte der Benutzer 1 Stufe unter dem Benutzer. Level 2 ist die Ebene 1 Punkte der Benutzer 2 Ebenen unter dem Benutzer, etc ...Wie berechnet man die Summe der Werte in einem Baum mit SQL

Die Berechnung geschieht einmal im Monat auf einem nicht produktiven Server, keine Sorgen über die Leistung.

Wie würde das SQL aussehen, um es zu tun?

Wenn Sie verwirrt sind, keine Sorge, ich bin es auch!

Benutzertabelle:

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 

Ausgang sollte sein:

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 Syntax und Funktionen vorzugsweise ...

Antwort

1

Ich würde sagen: eine gespeicherte Prozedur erstellen, wahrscheinlich hat die beste Leistung. Oder, wenn Sie eine maximale Anzahl von Ebenen haben, könnten Sie Unterabfragen erstellen, aber sie werden eine sehr poort Leistung haben.

(Oder Sie könnten MS SQL Server 2008 erhalten und die neue Hierarchie Funktionen bekommen ...;))

+0

Mein Denken auch, aber wie würde die Prozedur aussehen? – Jrgns

2

Wenn Sie Oracle DBMS wurden verwenden, die ziemlich einfach sein würde, da Oracle Baum Abfragen mit der CONNECT BY unterstützt/Beginnt mit Syntax. Für SQL Server denke ich, dass Sie möglicherweise Common Table Expressions nützlich finden

2

Bäume funktionieren nicht gut mit SQL. Wenn Sie sehr (sehr) sehr wenige Schreibzugriffe haben, könnten Sie die Baumimplementierung so ändern, dass sie verschachtelte Sätze verwendet, was diese Abfrage unglaublich einfach macht.

Beispiel (wenn ich mich nicht irre):

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

jedoch auf dem Baum alle Änderungen erfordern eine enorme Menge an Reihen zu berühren. Es ist wahrscheinlich besser, die Rekursion nur in Ihrem Client durchzuführen.

0

Sie haben ein paar Optionen:

  1. einen Cursor verwenden und einen rekursiven benutzerdefinierten Funktionsaufruf (es ist ziemlich langsam)
  2. eine Cache-Tabelle erstellen, auf INSERT aktualisieren, um einen Trigger (es ist aber die schnellste Lösung könnte problematisch sein, wenn Sie viele Aktualisierungen der Haupttabelle haben)
  3. Sie eine clientseitige rekursive Berechnung (vorzuziehen, wenn Sie nicht zu viele Datensätze haben)
1

wenn Sie arbeiten mit Bäumen, die in einer relationalen Datenbank gespeichert sind, würde ich vorschlagen, "verschachtelte Menge" oder "modifizierte Vororderbaum-Traversierung" zu betrachten. Die SQL wird so einfach wie das:

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

... und tun dies für jeden Knoten Sie interessiert sind

Vielleicht wird dies Ihnen helfen:. http://www.dbazine.com/oracle/or-articles/tropashko4 oder Google verwenden.

0

Sie können eine einfache rekursive Funktion schreiben, um die Aufgabe zu erledigen. Mein MSSQL ist ein wenig rostig, aber es würde wie folgt aussehen:

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, wie sieht die Funktion aus? – Jrgns

+0

Ich habe keine MSSQL-Installation hier, aber es würde so etwas wie gehen: getsum (parentNode int) Summe = Wert auswählen, wo Knoten = parentNode; foreach Zeile in ausgewählten Kindern aus der Tabelle, wobei parent = parentNode sum = sum + getsum (childnode) Sie würden es auf dem obersten Knoten nennen. –

1

SQL im Allgemeinen, wie andere gesagt, behandelt nicht gut solche Beziehungen. Typischerweise Tabelle Surrogat 'Beziehungen' benötigt (id, parent_id, eindeutigen Schlüssel auf (id, parent_id)), wobei:

  • Sie jedes Mal einen Datensatz in 'Tisch' hinzufügen, können Sie:

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

  • Logik Zyklen

  • stellen Sie sicher, zu vermeiden, dass Updates, Löschungen auf 'Beziehungen' mit Stored Procedures

dass Tabelle behandelt werden, möchten Sie:

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, das gibt dir die Ergebnisse, die du suchst, aber es gibt keine Garantie, dass ich nichts verpasst habe. Betrachten Sie es als Ausgangspunkt. Ich habe SQL 2005 zu diesem Zweck SQL 2000 unterstützt nicht 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

der folgenden Tabelle:

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

Und der folgende Betrag Tabelle:

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

Nur die Blätter (letzte level) Ids haben einen Wert definiert. Die SQL-Abfrage, um die Daten zu erhalten wie folgt aussieht:

;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 

dies in der Ausgabe führt:

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

Ich hoffe, das hilft.