I want to show the COUNT on every row how many BuildingID's there are within the MainGroundID's, while having to deal with a parent-child hierarchy that is unpivoted.
Unfortunately there is no logic within the way the GroundID and MainGroundID's are written (although it looks that way in my example, since I made an example dataset).
PMEBuilding
BuildingID, GroundID
1, 100
2, 100
3, 101
4, 201
5, 201
6, 201
7, 202
In reality the above table has 34K rows and 80+ fields.
The GroundID from the table above is N:1 to the table below via GroundID.
Within the PMEGroudn table Some GroundID's refer to a certain MainGroundID, which in turn also refer to Parents higher up in the hierarchy. The 'GrandParents' are those that have a NULL value as GroundID.
PMEGround
GroundID, MainGroundID
1, NULL --GrandParent
10, 1
100, 10
101, 10
2, NULL --GrandParent
20, 2
201, 20
202, 20
In reality the above table has 2K rows of which around 500 'GrandParents'.
I want this to be the end result:
MainGroundID MainGroundBuildingCount
1, 3
2, 7
The following code is what I used so far, but it doesn't work entirely yet:
;WITH UNPIVOT_HIERARCHY AS (
SELECT GROUNDID
,MAINGROUNDID
,PathID = CAST(GROUNDID AS VARCHAR(MAX))
FROM PMEGROUND
WHERE NULLIF(MainGroundID, '') IS NULL
UNION All
SELECT GROUNDID = r.GROUNDID
,MAINGROUNDID = r.MAINGROUNDID
,PathID = p.PathID+CONCAT(',',CAST(r.GROUNDID AS VARCHAR(MAX)))
FROM PMEGROUND r
JOIN UNPIVOT_HIERARCHY p ON r.MAINGROUNDID = p.GROUNDID
)
SELECT
B.Lvl3 AS 'MainGroundID' --This is the GrandParent, which works fine
,COUNT(PMEBUILDING.GROUNDID) OVER (PARTITION BY B.Lvl3) AS 'MainGroundCountBuildings'
FROM PMEGROUND
LEFT JOIN UNPIVOT_HIERARCHY
ON UNPIVOT_HIERARCHY.GROUNDID = PMEGROUND.GROUNDID
LEFT JOIN PMEBUILDING
ON PMEBUILDING.GROUNDID = PMEGROUND.GROUNDID
CROSS Apply (
SELECT Lvl1 = xDim.value('/x[3]','varchar(50)')
,Lvl2 = xDim.value('/x[2]','varchar(50)')
,Lvl3 = xDim.value('/x[1]','varchar(50)')
,Lvl4 = xDim.value('/x[4]','varchar(50)')
FROM ( VALUES (CAST('<x>' + REPLACE(PathID,',','</x><x>')+'</x>' AS xml))) B(xDim)
) B
GROUP BY B.Lvl3, PMEBUILDING.GROUNDID
Without the GROUP BY it gives duplicate MainGroundIDs, but the correct count.
With the GROUP BY it still gives duplicate MainGroundIDs but less, but the count is messed up now.
I want this to be the end result:
MainGroundID MainGroundBuildingCount
1, 3
2, 7
Don't you mean the end result should be?
MainGroundID MainGroundBuildingCount
1, 3
2, 4
Assuming, based on the given data, that there are 3 levels of hierarchy and PMEBuilding.GroundID contains only grandchildren I would use the following to achieve the end result:
select
gp.GroundID, count(distinct b.BuildingID)
from PMEGround gp
join PMEGround p on p.MainGroundID = gp.GRoundID
join PMEGround c on c.MainGroundID = p.GRoundID
join PMEBuilding b on b.GroundID = c.GroundID
where gp.MainGroundID is null
group by gp.GroundID
order by 1
Related
I've a data base with two tables.
Table Players Table Wins
ID Name ID Player_won
1 Mick 1 2
2 Frank 2 1
3 Sarah 3 4
4 Eva 4 5
5 Joe 5 1
I need a SQL query which show "The players who have not won any game".
I tried but I don't know even how to begin.
Thank you
You need all the rows from players that don't have corresponding rows in wins. For this you need a left join, filtering for rows that don't join:
select
p.id,
p.name
from Players p
left join Wins w on w.Player_won = p.id
where w.Player_won is null
You can also use not in:
select
id,
name
from Players
where id not in (select Player_won from Wins)
How about the MINUS set operator?
Sample data:
SQL> with players (id, name) as
2 (select 1, 'Mick' from dual union all
3 select 2, 'Ffrank' from dual union all
4 select 3, 'Sarah' from dual union all
5 select 4, 'Eva' from dual union all
6 select 5, 'Joe' from dual
7 ),
8 wins (id, player_won) as
9 (select 1, 2 from dual union all
10 select 2, 1 from dual union all
11 select 3, 4 from dual union all
12 select 4, 5 from dual union all
13 select 5, 1 from dual
14 )
Query begins here:
15 select id from players
16 minus
17 select player_won from wins;
ID
----------
3
SQL>
So, yes ... player 3 didn't win any game so far.
I think you should provide your attempts next time, but here you go:
select p.name
from players p
where not exists (select * from wins w where p.id = w.player_won);
MINUS is not the best option here because of not using indexes and instead performing a full-scan of both tables.
I've a data base with two tables.
You don't show the names or any definition of the tables, leaving me to make an educated guess about their structure.
I tried but I don't know even how to begin.
What exactly did you try? Possibly what you are missing here is the concept of a LEFT OUTER JOIN.
Assuming the tables are named player_table and wins_table, and have column names exactly as you showed, and that the player_won column is intended to express the number of games won by the player of that row's ID, and without knowing whether or not wins_table will have rows for players with zero wins… this should cover it:
select Name
from players_table pt
left join wins_table wt on (pt.ID = wt.ID)
-- Either this player is explicitly specified to have Player_won=0
-- or there is no row for this player ID in the wins table
-- (but excluding the possibility of an explicit NULL value, since its meaning would be unclear)
where Player_won = 0 or wt.ID is null;
As you can see from the variety of answers you've gotten, there are many ways to accomplish this.
One additional way to do this is to use COUNT in a correlated subquery, as in:
SELECT *
FROM PLAYERS p
WHERE 0 = (SELECT COUNT(*)
FROM WINS w
WHERE w.PLAYER_WON = p.ID)
db<>fiddle here
SELECT *
FROM Players p
INNER JOIN Wins w
ON p.ID = w.ID
WHERE w.players_won = 0
I have not done SQL in awhile but I think this might be right if you are looking for players with 0 wins
I have a table with the following columns:
ID_group, ID_elements
For example with the following records:
1, 1
1, 2
2, 2
2, 4
2, 5
2, 6
3, 7
And I have sets of the elements, for example: 1,2,5; 1,5,2; 1,2,4; 2,7;
I need to check (true or false) that exist a common group for the pairs of adjacent elements.
For example elements:
1,2,5 -> true [i.e. elements 1,2 has common group 1 and elements 2,5 has common group 2]
1,5,2 -> false [i.e. 1,5 do not have a common group unlike 5,2 (but the result is false due to 1,5 - false)]
1,2,4 -> true
2,7 -> false
First, we need a list of pairs. We can get this by taking your set as an array, turning each element into a row with unnest and then making pairs by matching each row with its previous row using lag.
with nums as (
select *
from unnest(array[1,2,5]) i
)
select lag(i) over() a, i b
from nums
offset 1;
a | b
---+---
1 | 2
2 | 5
(2 rows)
Then we join each pair with each matching row. To avoid counting duplicate data rows twice, we count only the distinct rows.
with nums as (
select *
from unnest(array[1,2,5]) i
), pairs as (
select lag(i) over() a, i b
from nums
offset 1
)
select
count(distinct(id_group,id_elements)) = (select count(*) from pairs)
from pairs
join foo on foo.id_group = a and foo.id_elements = b;
This works on any size array.
dbfiddle
Your query to check if elements in a set evaluate to true or not can be done via procedures/function. Set representation can be taken as a string and then splitting it to substring then returning the required result can use a record for multiple entries. For sql query, below is a sample that can be used as a workaround, you can try changing the below query based on your requirement.
select case when ( Select count(*)
from ( SELECT
id_group, count(distinct id_elements)
from table where
id_group
in (1,2,5)
group by ID_group having
id_elements
in (1,2,5)) =3 ) then "true" else "false"
end) from table;
#Schwern, thank you, it helped. But I have changed the condition join foo on foo.id_group = a, because as I understand, a is element's ID, not group's. I have changed the following section:
join foo fA on fA.id_elements = a
join foo fB on fB.id_elements = b and fA.group_id = fB.group_id;
EDIT - The problem was that the same item showed up multiple times in different spots in the BOM which then produced a result row for every instance instead of just one. This has been resolved. Thanks
I have the following code
WITH tBomCTE (ParentItem, ChildItem, WorkCentre, Operation, Quantity, ActualQuantity, ParentUnitWeight, ParentWeightUnitOfMeasure, ChildUnitWeight, ChildWeightUnitOfMeasure, BomLevel, MaterialClass, ParentItemSource) AS
(
SELECT
id.parentitem, id.ChildItem, id.WorkCentre, id.Operation,
id.Quantity, id.Quantity, id.ParentUnitWeight,
id.ParentWeightUnitOfMeasure, id.ChildUnitWeight,
id.ChildWeightUnitOfMeasure,
0 as BomLevel, id.MaterialClassCode, ParentItemSource
FROM
#tItemDenomalized id
WHERE
id.parentitem = '10054471'
UNION ALL
SELECT
id.parentitem, id.ChildItem, id.WorkCentre, id.Operation,
id.Quantity, CAST((id.Quantity * b.ActualQuantity) AS DECIMAL(19,8)), id.ParentUnitWeight,
id.ParentWeightUnitOfMeasure, id.ChildUnitWeight,
id.ChildWeightUnitOfMeasure,
BomLevel + 1,
id.MaterialClassCode, id.ParentItemSource
FROM
tBomCTE b
JOIN
#tItemDenomalized id ON b.ChildItem = id.parentitem
)
SELECT DISTINCT
'T1', ParentItem, ChildItem, WorkCentre, Operation, Quantity,
ActualQuantity,
COALESCE(ParentUnitWeight, 0), ParentWeightUnitOfMeasure,
COALESCE(ChildUnitWeight, 0), ChildWeightUnitOfMeasure, BomLevel,
MaterialClass, ParentItemSource
FROM
tBomCTE
The problem is that this code is producing multiple result rows. I have isolated it down to the cast((id.Quantity * b.ActualQuantity) as decimal(19,8))line.
Basically i am trying to build a Bill Of Materials (BOM) and we had a problem with the quantity not being added up appropriately. For example if we needed 2 of the parent item, the child item quantity only reflected what we needed for 1. Which was messing up costs.
So that line was added. It has never caused a problem but we just ran a test and now it is causing issues.
Specifically. I have a parent item, then child 1, then child 2. When I run this code i get 3 results for child 2 that all have the same path from the parent. So that doesn't make sense. And then the quantities are 22, 44, 66 for the 3 child 2 items respectively.
If i had to guess it looks like whats happening is that the quantity does get multiplied to the parent. Which then turns into 22. Then i gets multiplied by the next parent and instead of multiplying, it is creating a new row entirely.
Right now my solution is to update the quantities with code and then to delete all duplicate rows to get rid of the extra rows. But this is bad practice.
Why is it producing multiple rows instead of multiplying the parent to the current item?
Edit.
Here is my entire stored procedure that is causing the problem:
IF EXISTS (SELECT *
FROM sysobjects
WHERE id = object_id(N'[dbo].[spSAL_BomRecursive]')
AND OBJECTPROPERTY(id, N'IsProcedure') = 1 )
BEGIN
DROP PROCEDURE [dbo].[spSAL_BomRecursive];
END
GO
CREATE PROCEDURE [dbo].[spSAL_BomRecursive]
(
#SessionId varchar(50)
,#Item [dbo].[ItemType] = NULL
,#DebugLevel BIT = 0
,#CurrentOrStandardBOM nvarchar(1) = 'C'
)
AS
BEGIN
SET NOCOUNT ON;
--declare #Item varchar(30);
--set #item = '10029554';
Declare #tItemDenomalized TABLE (ParentItem nvarchar(30),
ChildItem nvarchar(30),
WorkCentre nvarchar(30) ,
Operation nvarchar(30),
Quantity decimal(19,8),
ParentUnitWeight decimal(18,9),
ParentWeightUnitOfMeasure nvarchar(3),
ChildUnitWeight decimal(18,9),
ChildWeightUnitOfMeasure nvarchar(3),
MaterialClassCode nvarchar(30),
ParentItemSource nvarchar(30));
Declare #CurrentOrStandardSuffix int
Set #CurrentOrStandardSuffix = Case when #CurrentOrStandardBOM = 'C' then 0 else 1 end
-- populate a table with all of the items, merging data to make the recursive SQL easier
Insert into #tItemDenomalized
select distinct i.item, coalesce(jm.item, ''), jr.wc, jr.oper_num, coalesce(jm.matl_qty, 0) as qty ,i.unit_weight as ParentUnitWeight, i.weight_units as ParentWeightUnitOfMeasure, i2.unit_weight as ChildUnitWeight, i2.weight_units as ChildWeightUnitOfMeasure, i.Uf_SalMaterialClassCode as MaterialClass, i.p_m_t_code
from item_mst i
left join jobroute_mst jr on i.job = jr.job and jr.suffix = #CurrentOrStandardSuffix
left join jobmatl_mst jm on jr.job = jm.job and jr.oper_num = jm.oper_num and jr.suffix = jm.suffix
left join item_mst i2 on coalesce(jm.item, '') = i2.item;
WITH tBomCTE ( ParentItem, ChildItem, WorkCentre, Operation, Quantity, ActualQuantity, ParentUnitWeight, ParentWeightUnitOfMeasure, ChildUnitWeight, ChildWeightUnitOfMeasure, BomLevel, MaterialClass, ParentItemSource )
AS
(
select id.parentitem, id.ChildItem, id.WorkCentre, id.Operation, id.Quantity, id.Quantity, id.ParentUnitWeight, id.ParentWeightUnitOfMeasure, id.ChildUnitWeight, id.ChildWeightUnitOfMeasure, 0 as BomLevel, id.MaterialClassCode, ParentItemSource
from #tItemDenomalized id
where id.parentitem = #item
UNION ALL
select id.parentitem, id.ChildItem, id.WorkCentre, id.Operation, id.Quantity, cast((id.Quantity * b.ActualQuantity) as decimal(19,8)) , id.ParentUnitWeight, id.ParentWeightUnitOfMeasure, id.ChildUnitWeight, id.ChildWeightUnitOfMeasure, BomLevel+1, id.MaterialClassCode, id.ParentItemSource
from tBomCTE b
join #tItemDenomalized id on b.ChildItem = id.parentitem
)
insert into tSAL_Bom
([SessionId],[ParentItem],[ChildItem],[WorkCentre],[Operation],[Quantity],[ActualQuantity],[ParentUnitWeight],[ParentWeightUnitOfMeasure],[ChildUnitWeight],[ChildWeightUnitOfMeasure],[BomLevel],[MaterialClassCode],[ParentItemSource])
SELECT distinct #SessionId, ParentItem, ChildItem, WorkCentre, Operation, Quantity, ActualQuantity, coalesce(ParentUnitWeight, 0), ParentWeightUnitOfMeasure, coalesce(ChildUnitWeight, 0), ChildWeightUnitOfMeasure, BomLevel, MaterialClass, ParentItemSource
FROM tBomCTE
-- cleanup the table from yesterday
delete from tSAL_Bom
where CreatedOn < Getdate()-1
RETURN 0;
END
GO
The problematic data rows are the following
SortingOrder DepthLevel ItemOrWorkCenterNumber BaseQuantity Quantity
[10054471] 0 10054471 1 1
[10054471][1605][10008773] 1 10008773 1 2
[10054471][1605][10008773][1100][10024306] 2 10024306 2 4
[10054471][1605][10008773][1100][10024306][1005][10030273] 3 10030273 11 22
[10054471][1605][10008773][1100][10024306][1005][10030273] 3 10030273 11 44
[10054471][1605][10008773][1100][10024306][1005][10030273] 3 10030273 11 66
So as you can hopefully see. There should be 1 row that looks like this
[10054471][1605][10008773][1100][10024306][1005][10030273] 3 10030273 11 88
Because the main parent requires 1. The first child requires 2. So our multiplier is 2. Our next parent requires 2 normally but with the multiplier requires 4 which makes our multiplier now 8. This line is correct. Then the next line should be the base quantity of 11 times the multiplier of 8. So 88. But instead i am getting a row that is multiplied by 2, and 4 and 6.
This isn't really an answer to your question, but an attempt to break down the massive amount of logic into something reproducible.
I wrote a very quick, self-encapsulated query that "sort of" does what I think you are trying to do. Maybe you could do something similar to explain what the differences between my logic and your logic are?
WITH Base AS (
SELECT 1 AS id, NULL AS parent, 1 AS multiplier
UNION ALL
SELECT 2 AS id, 1 AS parent, 2 AS multiplier
UNION ALL
SELECT 3 AS id, 2 AS parent, 4 AS multiplier),
Recurs AS (
SELECT
id,
1 AS depth,
multiplier
FROM
Base
WHERE
id = 1
UNION ALL
SELECT
b.id,
depth + 1 AS depth,
b.multiplier * r.multiplier AS multiplier
FROM
Base b
INNER JOIN Recurs r ON r.id = b.parent),
SecondRecurs AS (
SELECT
id,
depth,
multiplier
FROM
Recurs
UNION ALL
SELECT
p.parent,
s.depth,
s.multiplier
FROM
SecondRecurs s
INNER JOIN Base b ON b.id = s.id
INNER JOIN Base p ON p.id = b.parent),
Ordered AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY depth DESC, id) AS order_id
FROM
SecondRecurs)
SELECT
id,
depth,
multiplier
FROM
Ordered
WHERE
order_id = 1;
So how does this work?
First I make some test data:
id parent multiplier
1 NULL 1
2 1 2
3 2 4
Then I use a recursive CTE to get the depth/ multiplier, using similar logic to your example:
id depth multiplier
1 1 1
2 2 2
3 3 8
But I deliberately let this run without worrying about rolling up children into parents, so now I have a second stage to get this into some sort of order:
id depth multiplier order_id
1 3 8 1
3 3 8 2
NULL 2 2 3
2 2 2 4
1 1 1 5
Finally I can pick out the row I want, ignoring the "partial" results that you seem to be getting in your query?
id depth multiplier
1 3 8
Does this help at all?
for a table such as:
employeeID | groupCode
1 red111
2 red111
3 blu123
4 blu456
5 red553
6 blu423
7 blu341
how can I count the number of employeeIDs that are in parent groups (such as red or blu, but there are many more groups in the real table) that have a total number of group members greater than 2 (so all those with blu in this particular example) excluding themselves.
To expand: groupCode consists of a parent group (three letters), followed by some numbers for the subgroup.
using a self-join, or at least without using a group by statement.
So far I have:
SELECT T1.employeeID
FROM TABLE T1, TABLE T2
WHERE T1.groupCode <> T2.groupCode
AND SUBSTR(T1.groupCode, 1, 3) = SUBSTR(T2.gorupCode, 1, 3);
but that doesn't do much for me...
Add an index on the first 3 characters of EMPLOYEE.
Then try this one:
SELECT ed.e3
, COUNT(*)
FROM EMPLOYEE e
JOIN
( SELECT DISTINCT
SUBSTR(groupCode, 1, 3) AS e3
FROM EMPLOYEE
) ed
ON e.groupCode LIKE CONCAT(ed.e3, '%')
GROUP BY ed.e3
HAVING COUNT(*) >= 3 --- or whatever is wanted
What about
SELECT substring(empshirtno, 1, 3),
Count(SELECT 1 from myTable as myTable2
WHERE substring(mytable.empshirtno, 1, 3) = substring(mytable2.empshirtno, 1, 3))
FROM MyTable
GROUP BY substring(mytable2.empshirtno, 1, 3)
maybe counting from a subquery is speedier with an index
I need to sum points on each level earned by a tree of users. Level 1 is the sum of users' points of the users 1 level below the user. Level 2 is the Level 1 points of the users 2 levels below the user, etc...
The calculation happens once a month on a non production server, no worries about performance.
What would the SQL look like to do it?
If you're confused, don't worry, I am as well!
User table:
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
Output should be:
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 and functions preferably...
If you were using Oracle DBMS that would be pretty straightforward since Oracle supports tree queries with the CONNECT BY/STARTS WITH syntax. For SQL Server I think you might find Common Table Expressions useful
Trees don't work well with SQL. If you have very (very very) few write accesses, you could change the tree implementation to use nested sets, that would make this query incredibly easy.
Example (if I'm not mistaken):
SELECT SUM(points)
FROM users
where left > x and right < y
However, any changes on the tree require touching a massive amount of rows. It's probably better to just do the recursion in you client.
I would say: create a stored procedure, probably has the best performance.
Or if you have a maximum number of levels, you could create subqueries, but they will have a very poort performance.
(Or you could get MS SQL Server 2008 and get the new hierarchy functions... ;) )
SQL in general, like others said, does not handle well such relations. Typically, a surrogate 'relations' table is needed (id, parent_id, unique key on (id, parent_id)), where:
every time you add a record in 'table', you:
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];
have logic to avoid cycles
make sure that updates, deletions on 'relations' are handled with stored procedures
Given that table, you want:
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;
Ok, this gives you the results you are looking for, but there are no guarantees that I didn't miss something. Consider it a starting point. I used SQL 2005 to do this, SQL 2000 does not support CTE's
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
If you are working with trees stored in a relational database, I'd suggest looking at "nested set" or "modified preorder tree traversal". The SQL will be as simple as that:
SELECT id,
SUM(value) AS value
FROM table
WHERE left>left\_value\_of\_your\_node
AND right<$right\_value\_of\_your\_node;
... and do this for every node you are interested in.
Maybe this will help you:
http://www.dbazine.com/oracle/or-articles/tropashko4 or use google.
You have a couple of options:
Use a cursor and a recursive user-defined function call (it's quite slow)
Create a cache table, update it on INSERT using a trigger (it's the fastest solution but could be problematic if you have lots of updates to the main table)
Do a client-side recursive calculation (preferable if you don't have too many records)
You can write a simple recursive function to do the job. My MSSQL is a little bit rusty, but it would look like this:
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
The following table:
Id ParentId
1 NULL
11 1
12 1
110 11
111 11
112 11
120 12
121 12
122 12
123 12
124 12
And the following Amount table:
Id Val
110 500
111 50
112 5
120 3000
121 30000
122 300000
Only the leaves (last level) Id's have a value defined.
The SQL query to get the data looks like:
;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
this results in the output:
Id Amount
1 333555
11 555
12 333000
110 500
111 50
112 5
120 3000
121 30000
122 300000
123 0
124 0
I hope this helps.