Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2000 Forums
 Transact-SQL (2000)
 Rolling Budget dollars up an Org chart

Author  Topic 

X002548
Not Just a Number

15586 Posts

Posted - 2003-02-28 : 14:38:23
Don't know if this is the place to post this...but,

If I have Budget $ at the lowest level Organization:



ORG BUDGET$
A $10
B $15
C $27
X $1000
And I have an Organization "Chart"

ORG PARENT ORG
A D
B E
C D
D E
E F
X F

You'll notice a tree that has many different branches.

I need to take the lowest level $ and sum them through their parents. Such that:

ORG SUMMED BUDGET
D $25
E $52
F $1052

I've actually built a skeletal hierarch, with a level code table, and I'm stuck at a point of trying to apply updates. Because I think it's important to know what level you're on I've been able to build:

ORG PARENT ORG LEVEL
A D 4
C D 4
B E 4
X F 4
D E 3
E F 2
F Null 1

I know I can get a result set for what I want, such that

SELECT PARENT_ORG, SUM($) FROM TABLE WHERE LEVEL = 4 GROUP BY PARENT

But I can't figure how to updatet the parent rows with their childrens summed dollars. That means A and C would update D, B would update E and so on. Another problem then comes when I go up a level. E now needs to add the sum of D, plus what I've already added From B in the previous step.

Now I'm not making this up, and if anyone has worked with Smartstream general ledger stuff, the fact that they let users do whatever they want causes this mess (all in the name of flexibility).

IF IT CAN HAPPEN IT WILL HAPPEN. This is good adive when building a DB.

Any help will be GREATLY appreciated. I'm sure it can be done with cursors....(Nooooooooooooooooo not the cursors.....ahhhhhhh)

Thanks

Brett

PS I Hope any of this makes sense




macka
Posting Yak Master

162 Posts

Posted - 2003-03-02 : 14:01:58
Joe Celko's nested set model is ideal for this kind of operation, although it would require your adjaceny model being converted/modified.

The following is based on employees salaries and how much each employee is responsible for. It should be trivial to convert for your requirements.

Snipped from Celko's "classic" post:
[url]http://groups.google.com/groups?q=celko+salary&hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=89k4h0%24ard%241%40nnrp1.deja.com&rnum=1[/url]

quote:


CREATE TABLE Personnel
(emp CHAR(10) PRIMARY KEY,
boss CHAR(10), -- this column is unneeded & denormalizes the
table
salary DECIMAL(6,2) NOT NULL,
lft INTEGER NOT NULL,
rgt INTEGER NOT NULL);

Personnel
emp boss salary lft rgt
===================================
Albert NULL 1000.00 1 12
Bert Albert 900.00 2 3
Chuck Albert 900.00 4 11
Donna Chuck 800.00 5 6
Eddie Chuck 700.00 7 8
Fred Chuck 600.00 9 10




and then....

quote:


3. Add a GROUP BY and aggregate functions to these basic queries and
you have hierarchical reports. For example, the total salaries which
each employee controls:

SELECT P2.emp, SUM(P1.salary)
FROM Personnel AS P1, Personnel AS P2
WHERE P1.lft BETWEEN P2.lft AND P2.rgt
GROUP BY P2.emp;




macka.

--
There are only 10 types of people in the world - Those who understand binary, and those who don't.
Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 2003-03-03 : 10:47:56
Thanks. But does this address where multiple levels? What happens for example if a parent has multiplt "lineages".

Go to Top of Page

macka
Posting Yak Master

162 Posts

Posted - 2003-03-03 : 11:32:49
Its not entirely clear what you mean by multiple "lineages". Can a node have more than one parent ? OR do you mean, does it take into account the budgets across multiple levels. If its the latter then the answer is yes. I've added the lft and rgt columns to the table.

--DDL
CREATE TABLE org(
orgID CHAR,
parent CHAR null,
budget MONEY,
lft INT,
rgt INT
)

--DML
INSERT INTO org
SELECT 'F',NULL,0,1,14
UNION
SELECT 'X','F',1000,2,3
UNION
SELECT 'E','F',0,4,13
UNION
SELECT 'B','E',15,5,6
UNION
SELECT 'D','E',0,7,12
UNION
SELECT 'A','D',10,8,9
UNION
SELECT 'C','D',27,10,11

--output the results
SELECT o2.orgID,SUM(o1.budget) AS total_budget
FROM org o1,org o2
WHERE o1.lft BETWEEN o2.lft AND o2.rgt
GROUP BY o2.orgID

The result of this query is:

orgID total_budget
--------------------
A 10.0000
B 15.0000
C 27.0000
D 37.0000
E 52.0000
F 1052.0000
X 1000.0000

macka.


--
There are only 10 types of people in the world - Those who understand binary, and those who don't.
Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 2003-03-03 : 13:33:53
VERY COOL !!

I've just order the SQL For Smarties Book and I'm reading the link online, But I'm still lost on the process to derive Left and Right.

THANKS FOR YOUR HELP

Brett

9-)

Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 2003-03-04 : 09:38:50
The book should come in a couple of days, but any idea how to derive LEFT and RIGHT would be appreciated. Otherwise I'll work on something else till then.

Thanks

Brett

8-)


Go to Top of Page

macka
Posting Yak Master

162 Posts

Posted - 2003-03-04 : 10:25:49
I initially had some problems visualising the tree using the system, but once you have mastered it on paper for small trees you can mentally apply the principle to larger trees.

Draw a tree with 3 nodes on a piece of paper - 1 root and 3 children. For example

A
/ \
B C
/
D

Label the left hand side of the root (A) with the number 1. That gives us:

(1) A
/ \
B C
/
D

We now need to move down the tree from left to right. So the first node we come to is B. Label the left hand side of the tree with the number 2.

(1) A
/ (2) B C
/
D


As I said we are moving from left to right so keep going left and we come to node D. Label the left hand side of this with 3.

(1) A
/ \
(2) B C
/
(3) D

We can no longer go left as we are at the bottom of the tree - so we now need to travel back "up" on the "right" and label the nodes on the right hand side.

(1) A
/ \
(2) B C
/
(3) D (4)


Up again it is:

(1) A
/ \
(2) B (5) C
/
(3) D (4)


Having now labelled node B on the right, we are able to travel back down on the left to C. So we get :

(1) A
/ \
(2) B(5) (6)C
/
(3) D (4)

At the bottom of the tree again, so up on the right we go:

(1) A
/ \
(2) B(5) (6)C (7)
/
(3) D (4)

and then finally:

(1) A (8)
/ \
(2) B(5) (6)C (7)
/
(3) D (4)


As you can see we have a counter which is incremented for each side of each node of the tree.

Do you remember doing dot-to-dots as a kid? - well if you join up the numbers from 1 through 8 you'll see that you have travelled around the tree from left to right, visiting each node twice.

Now for the code - this will apply the above principle and convert you adjancey model to the nested set model.

(courtesy of Mr. Celko - slightly modified for SQL Server by moi)


-- Tree holds the adjacency model
CREATE TABLE Tree
(node CHAR(10) NOT NULL,
parent CHAR(10));

-- THIS IS BASED ON YOUR ORG DB
INSERT INTO tree(node,parent)
SELDCT OrgID,Parent
FROM ORG

-- Stack starts empty, will holds the nested set model
CREATE TABLE Stack
(stack_top INTEGER NOT NULL,
node CHAR(10) NOT NULL,
lft INTEGER,
rgt INTEGER);

DECLARE @counter INTEGER;
DECLARE @max_counter INTEGER;
DECLARE @current_top INTEGER;

SET @counter = 2;
SET @max_counter = 2 * (SELECT COUNT(*) FROM Tree);
SET @current_top = 1;

--clear the stack
DELETE FROM Stack;

-- push the root
INSERT INTO Stack
SELECT 1, node, 1, @max_counter
FROM Tree
WHERE parent IS NULL;

-- delete rows from tree as they are used
DELETE FROM Tree WHERE parent IS NULL;

WHILE @counter <= @max_counter- 1
BEGIN
IF EXISTS (SELECT *
FROM Stack AS S1, Tree AS T1
WHERE S1.node = T1.parent
AND S1.stack_top = @current_top)

BEGIN -- push when top has subordinates and set lft value

INSERT INTO Stack
SELECT (@current_top + 1), MIN(T1.node), @counter, NULL
FROM Stack AS S1, Tree AS T1
WHERE S1.node = T1.parent
AND S1.stack_top = @current_top;

-- delete rows from tree as they are used
DELETE FROM Tree
WHERE node = (SELECT node
FROM Stack
WHERE stack_top = @current_top + 1);
-- housekeeping of stack pointers and counter
SET @counter = @counter + 1;
SET @current_top = @current_top + 1;
END
ELSE
BEGIN -- pop the stack and set rgt value
UPDATE Stack
SET rgt = @counter,
stack_top = -stack_top -- pops the stack
WHERE stack_top = @current_top
SET @counter = @counter + 1;
SET @current_top = @current_top - 1;
END;
END
-- the top column is not needed in the final answer
SELECT node, lft, rgt FROM Stack;

--UPDATE lft/rgt node values in your table
UPDATE Org
SET Org.lft = stack.lft,Org.rgt = stack.rgt
FROM Org INNER JOIN stack ON node = OrgID

DROP TABLE tree
DROP TABLE stack


macka.

--
There are only 10 types of people in the world - Those who understand binary, and those who don't.

Edited by - macka on 03/04/2003 10:30:05

Edited by - macka on 03/04/2003 10:30:53

Edited by - macka on 03/04/2003 10:31:34
Go to Top of Page

Sitka
Aged Yak Warrior

571 Posts

Posted - 2003-03-07 : 07:40:11
Given

Personnel
emp boss salary lft rgt
===================================
Albert NULL 1000.00 1 12
Bert Albert 900.00 2 3
Chuck Albert 900.00 4 11
Donna Chuck 800.00 5 6
Eddie Chuck 700.00 7 8
Fred Chuck 600.00 9 10


Can someone translate this...

SELECT P2.*
FROM Personnel AS P1, Personnel AS P2
WHERE P1.lft BETWEEN P2.lft AND P2.rgt
AND P1.emp = :myemployee;

to ANSI.




Voted best SQL forum nickname...."Tutorial-D"
Go to Top of Page

jsmith8858
Dr. Cross Join

7423 Posts

Posted - 2003-03-07 : 08:00:40
I think :myemployee is the input parameter.

so, unless I'm mistaken, I believe the following is ANSI standard:

SELECT P2.*
FROM Personnel AS P1
INNER JOIN
Personnel AS P2
ON P1.lft BETWEEN P2.lft AND P2.rgt
WHERE P1.emp = @MyEmployee;

Usually, you just remove all join conditions from the WHERE clause and re-write them using INNER/OUTER JOIN syntax.


- Jeff
Go to Top of Page

Sitka
Aged Yak Warrior

571 Posts

Posted - 2003-03-07 : 08:40:16
no mistakes

Thank-you

Voted best SQL forum nickname...."Tutorial-D"
Go to Top of Page
   

- Advertisement -