I have a self-referencing table with content like this:
Self-referencing parent table
ID ParentID Name
---------------------
1 John
2 1 Mike
3 2 Erin
4 1 Janie
5 Eric
6 5 Peter
The tree hierarchy should look like this
John
Mike
Erin
Janie
Eric
Peter
And a child table that stores the leaf of parent table that looks like this:
ID Sales
3 100
3 100
4 200
4 200
6 300
6 300
6 300
I'm trying to roll-up the sum from the leaf node up to the hierarchy so it would return as ..
ID Name Sum
1 John 800
2 Mike 200
3 Erin 200
4 Janie 400
5 Eric 900
6 Peter 900
Any ideas how to achieve this in sql 2008? Thanks in advance.
EDIT All aggregation moved out of the CTE
WITH
tree AS
(
SELECT
id AS root_id,
name AS root_name,
id AS leaf_id
FROM
yourTreeTable
UNION ALL
SELECT
tree.root_id AS root_id,
tree.name AS root_name,
yourTreeTable.id AS leaf_id
FROM
tree
INNER JOIN
yourTreeTable
ON tree.leaf_id = yourTreeTable.ParentID
)
SELECT
tree.root_id,
tree.root_name,
COALESCE(SUM(yourScoresTable.score), 0) AS total
FROM
tree
LEFT JOIN
yourScoresTable
ON yourScoresTable.ID = tree.leafID
GROUP BY
tree.root_id,
tree.root_name
Here it is:
Let's supose this schema:
​create table #parent (
ID int,
ParentID int,
Name varchar(50) );
create table #child (
ID int,
Sales int );
The query is self explained:
WITH
tree AS
(
SELECT
id as id_parent,
id as id
FROM
#parent
UNION ALL
SELECT
tree.id_parent as id_parent,
#parent.id AS id
FROM
tree
INNER JOIN
#parent
ON tree.id = #parent.ParentID
)
SELECT
#parent.id,
#parent.name,
COALESCE(SUM(#child.Sales), 0) AS total
FROM
#parent
LEFT JOIN
tree
ON #parent.ID = tree.id_parent
LEFT JOIN
#child on tree.id = #child.id
GROUP BY
#parent.id,
#parent.name
CTE returns a list of 'leafs' for each employee (#parent), then query sums all sales for this 'leafs'. You can test it running.
EDITED
Query is fixed.
Related
I need to get the name of all of the employees that depends of a person directly or indirectly. Using the query in this example (from https://rextester.com/WGVRGJ67798),
create table employee(
id int not null,
employee varchar(10) not null,
boss int null
)
insert into employee values
(1,'Anna',null),
(2,'Bob',1),
(3,'Louis',1),
(4,'Sara',2),
(5,'Sophie',2),
(6,'John',4);
with boss as (
select id, employee, boss, cast(null as varchar(10)) as name
from employee
where boss is null
union all
select e.id, e.employee, b.id, b.employee
from employee e
join boss b on b.id = e.boss
)
select * from boss
I can get this result:
However, I need to see this:
It would be like showing all the possible relations between a person an all of those employees "below" him or her.
You can reverse the logic: instead of starting from the boss (the root) and going towards employees (the leafs), you could start from the leafs and walk toward the root. This lets you generate the intermediate relations as you go:
with cte as (
select e.id, e.employee, e.boss, b.employee name, b.boss new_boss
from employee e
left join employee b on b.id = e.boss
union all
select c.id, c.employee, c.new_boss, e.employee, e.boss
from cte c
join employee e on e.id = c.new_boss
)
select id, employee, boss, name
from cte
order by id, boss
Demo on DB Fiddle:
id | employee | boss | name
-: | :------- | ---: | :---
1 | Anna | null | null
2 | Bob | 1 | Anna
3 | Louis | 1 | Anna
4 | Sara | 1 | Anna
4 | Sara | 2 | Bob
5 | Sophie | 1 | Anna
5 | Sophie | 2 | Bob
6 | John | 1 | Anna
6 | John | 2 | Bob
6 | John | 4 | Sara
I like hierarchyid for this sort of thing.
use tempdb;
drop table if exists employee;
drop table if exists #e;
create table employee(
id int not null,
employee varchar(10) not null,
boss int null
)
insert into employee values
(1,'Anna',null),
(2,'Bob',1),
(3,'Louis',1),
(4,'Sara',2),
(5,'Sophie',2),
(6,'John',4);
with boss as (
select id, employee, boss,
cast(concat('/', id, '/') as hierarchyid) as h
from employee
where boss is null
union all
select e.id, e.employee, b.id,
cast(concat(b.h.ToString(), e.id, '/') as hierarchyid)
from employee e
join boss b on b.id = e.boss
)
select *
into #e
from boss
select e.id, e.employee, b.id, b.employee, b.h.ToString()
from #e as e
left join #e as b
on e.h.IsDescendantOf(b.h) = 1
and e.id <> b.id;
I took your code mostly as is and changed the following things:
Rather than keeping track of the boss in the recursive CTE, I'm building a hierarchyid path that leads all the way back to the root of the hierarchy.
Shoved the results of the cte into a temp table
Selected from the temp table, using a self-join where the join criteria are "where the inner table's notion of employee is anywhere in the management chain for the outer table".
Note, for the join, I'm excluding the case where the employee reports to themselves; you cannot be your own boss in this situation (even though the IsDescendantOf method would suggest otherwise!).
Something like this. There are two recursions. First, to get the h_level which with the first recursion represent boss-->employee relationships. Second, treats each row from the first as the leaf node in a new recursion to find direct and indirect hierarchical relationships.
Data
drop table if exists Employee;
go
create table employee(
id int not null,
employee varchar(10) not null,
boss int null)
insert into employee values
(1,'Anna',null),
(2,'Bob',1),
(3,'Louis',1),
(4,'Sara',2),
(5,'Sophie',2),
(6,'John',4);
Query
;with
boss(id, employee, boss, h_level) as (
select id, employee, boss, 0
from employee
where boss is null
union all
select e.id, e.employee, b.id, b.h_level+1
from employee e
join boss b on b.id = e.boss),
downlines(id, employee, boss, h_level, d_level) as (
select id, employee, boss, h_level, 0
from boss
union all
select b.id, b.employee, d.id, d.h_level, d.d_level+1
from boss b
join downlines d on d.id = b.boss)
select *
from downlines
order by h_level, d_level;
Output
id employee boss h_level d_level
1 Anna NULL0 0
2 Bob 1 0 1
3 Louis 1 0 1
4 Sara 2 0 2
5 Sophie 2 0 2
6 John 4 0 3
2 Bob 1 1 0
3 Louis 1 1 0
4 Sara 2 1 1
5 Sophie 2 1 1
6 John 4 1 2
4 Sara 2 2 0
5 Sophie 2 2 0
6 John 4 2 1
6 John 4 3 0
i have a table which has parent child relationship like this - Isfinal column suggests that it is the final element of that level
ID name ParentId Isfinal
1 abc 0 No
2 acd 1 No
3 ads 1 No
4 xyz 2 No
5 xxy 2 Yes
6 plm 3 No
7 ytr 4 Yes
8 lks 6 Yes
I am trying to write a dynamic query which will give the child element of that ID.
E.G.
If I select 2 then it should give the result as -
ID name ParentId Isfinal
4 xyz 2 No
5 xxy 2 Yes
7 ytr 4 Yes
Is it possible with self join?
Using a recursive CTE , you can solve this.
DECLARE #TABLE TABLE
( ID int
,name nvarchar(200)
,ParentId int
,Isfinal nvarchar(20)
)
INSERT INTO #TABLE
VALUES (1,'abc',0,'No'),(2,'acd',1,'No'),(3,'ads',1,'No'),
(4,'xyz',2,'No'),(5,'xxy',2,'Yes'),(6,'plm',3,'No'),
(7,'ytr',4,'Yes'),(8,'lks',6,'Yes')
DECLARE #ID INT = 2
;WITH CTE
AS
(
SELECT ID,name,ParentId,Isfinal
FROM #TABLE
WHERE ParentId = #ID
UNION ALL
SELECT T.ID,T.name,T.ParentId,T.Isfinal
FROM #TABLE T
INNER JOIN CTE C ON C.ID = T.ParentId
)
SELECT * FROM CTE
excuse the bad title but I couldn't find a good way to express what I want in abstract terms.
Anyway I have 3 tables
tbl_product:
PID | productname
1 | product 1
2 | product 2
3 | product 3
4 | product 4
..
tbl_categories, motherCategory allows me to nest categories:
CID | categoriename | motherCategory
1 | electronics | NULL
2 | clothing | NULL
3 | Arduino | 1
4 | Casings, extra's | 3
..
tbl_productInCategory PID and CID are foreign keys to PID and CID in tbl_product and tbl_categories respectively. A product can have multiple categories assigned to it so PID can occur more than once in this table.
PID | CID
1 | 1
2 | 1
3 | 3
4 | 4
Now I have a query that returns all categories if I give the mothercategory.
What I want to do is show ONLY the categories that have products in them recursively.
for instance on the example data above I show all categories(motherCategory is null), I want it to return only electronics since there are no products category 2, clothing.
However the problem I am having is that I also want this to work recursively. Consider this tbl_productInCategory:
PID | CID
1 | 2
2 | 2
3 | 2
4 | 4
Now it should return both clothing and electronics even though there are no products in electronics, because there are products in the nested category arduino->Casings, extra's. If I show all categories with motherCategory, electronics it should also return arduino.
I can't figure out how to do this and any help or pointers are appreciated.
First you should select all categories where products exist. On the next steps select mother categories.
WITH CTE AS
(
SELECT tbl_categories.*
FROM
tbl_categories
JOIN tbl_productInCategory on tbl_productInCategory.CID = tbl_categories.CID
UNION ALL
SELECT tbl_categories.*
FROM tbl_categories
JOIN CTE on tbl_categories.CID = CTE.motherCategory
)
SELECT DISTINCT * FROM CTE
Use a recursive CTE to get a derived table of your category tree, and then INNER JOIN it to your ProductCategory table.
It's not something I've done before, but some googling indicates it is possible.
https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
The semantics of the recursive execution is as follows:
Split the CTE expression into anchor and recursive members.
Run the anchor member(s) creating the first invocation or base result set (T0).
Run the recursive member(s) with Ti as an input and Ti+1 as an output.
Repeat step 3 until an empty set is returned.
Return the result set. This is a UNION ALL of T0 to Tn.
USE AdventureWorks2008R2;
GO
WITH DirectReports (ManagerID, EmployeeID, Title, DeptID, Level)
AS
(
-- Anchor member definition
SELECT e.ManagerID, e.EmployeeID, e.Title, edh.DepartmentID,
0 AS Level
FROM dbo.MyEmployees AS e
INNER JOIN HumanResources.EmployeeDepartmentHistory AS edh
ON e.EmployeeID = edh.BusinessEntityID AND edh.EndDate IS NULL
WHERE ManagerID IS NULL
UNION ALL
-- Recursive member definition
SELECT e.ManagerID, e.EmployeeID, e.Title, edh.DepartmentID,
Level + 1
FROM dbo.MyEmployees AS e
INNER JOIN HumanResources.EmployeeDepartmentHistory AS edh
ON e.EmployeeID = edh.BusinessEntityID AND edh.EndDate IS NULL
INNER JOIN DirectReports AS d
ON e.ManagerID = d.EmployeeID
)
-- Statement that executes the CTE
SELECT ManagerID, EmployeeID, Title, DeptID, Level
FROM DirectReports
INNER JOIN HumanResources.Department AS dp
ON DirectReports.DeptID = dp.DepartmentID
WHERE dp.GroupName = N'Sales and Marketing' OR Level = 0;
GO
I have a table with categories, each category as an ID, a Name and a ParentID. The problem is that there are 3 levels, parent categories, sub categories and child categories.
I can extract parent categories with a simple SELECT and a WHERE ParentID IS NULL clause as such:
SELECT *
FROM Category
WHERE ParentID IS NULL
However, a WHERE ParentID IS NOT NULL clause will return both, sub categories as well as child categories.
I'm looking for a way to extract only sub categories, and only child categories.
Typically, for these sort of problems, it is better to use the Recursive Queries Using Common Table Expressions. Something like so:
;WITH CategoriesTree(CategoryID, CategoryName, ParentName, CategoryLevel)
AS
(
SELECT
c.ID,
c.Name,
CAST('No Parent' AS VARCHAR(50)) AS ParentName,
0 AS CategoryLevel
FROM #Categories c
WHERE c.ParentID IS NULL
UNION ALL
SELECT c.ID, c.Name, p.CategoryName, p.CategoryLevel + 1
FROM CategoriesTree p
INNER JOIN #Categories c ON c.ParentID = p.CategoryID
)
SELECT *
FROM CategoriesTree
Where CategoryLevel = some id;
SQL Fiddle Demo
This will give you:
CATEGORYID CATEGORYNAME PARENTNAME CATEGORYLEVEL
1 Root Cateogry No Parent 0
2 Sub Cateogry 1 Root Cateogry 1
3 Sub Cateogry 2 Root Cateogry 1
4 Sub Cateogry 3 Root Cateogry 1
8 sub Cateogry 1 of 3 Sub Cateogry 3 2
7 Sub Cateogry 1 of 2 Sub Cateogry 2 2
5 Sub Cateogry 1 of 1 Sub Cateogry 1 2
6 sub Cateogry 2 of 1 Sub Cateogry 1 2
How does this work?
Using this query, you can control what level of categories you want to select. For instance, for the sample data, I used in the previous demo, here is the categories tree:
1: RootCategory Category Level: 0
|
|
----------------------------
| | |
| | |
2: Sub1 3: Sub2 4: sub3 Category Level: 1
| | |
------------ | |
| | | |
| | | |
5: Sub1of1 6: Sub2of1 7: sub1of2 8: sub1of3 Category Level: 2
This query will give you this categories tree with the new generated CategoryLevel column.
Note that: In the sample data I used in the demo, there was only one parent category (the categories with parentid IS NULL). However, the query will for work fine in case there were a lot of parent categories. And this because of the anchor query of the CTE, which is:
SELECT
c.ID,
c.Name,
CAST('No Parent' AS VARCHAR(50)) AS ParentName,
0 AS CategoryLevel
FROM #Categories c
WHERE c.ParentID IS NULL;
Then, you can use the generated column CategoryLevel to select only the child categories of the level that you are interested in.
For example, if you need to select only the sub categories of the first sub categories of the root category, you can get these categories using the predicate CategoryLevel = 2:
;WITH CategoriesTree(CategoryID, CategoryName, ParentName, CategoryLevel)
AS
(
...
)
SELECT *
FROM CategoriesTree
WHERE CategoryLevel = 2;
This will give you:
CATEGORYID CATEGORYNAME PARENTNAME CATEGORYLEVEL
8 sub Cateogry 1 of 3 Sub Cateogry 3 2
7 Sub Cateogry 1 of 2 Sub Cateogry 2 2
5 Sub Cateogry 1 of 1 Sub Cateogry 1 2
6 sub Cateogry 2 of 1 Sub Cateogry 1 2
SQL Fiddle Demo
First level categories - you have it:
SELECT *
FROM Category
WHERE ParentID IS NULL
For the second level categories you can try:
SELECT * FROM Category
WHERE ParentID IN (SELECT ID FROM Category WHERE ParentID IS NULL).
For the third:
SELECT * FROM Category
WHERE ParentID IN (SELECT ID FROM Category WHERE ParentID IS NOT NULL)
(Not tested)
How about something like:
-- Root parents
select c.* from categories c where c.ParentID is null
-- Second level. Select where parentid is a root category.
select c.* from categories c
where c.ParentID in (select c1.ID from categories c1 where c1.ParentID is null);
-- Third level. Select where parentid is a second level category
with second_level_cats (ID) as (
select c.ID from categories c
where c.ParentID in (select c1.ID from categories c1 where c1.ParentID is null)
)
select c.* from categories c
where c.ParentID in (select l2.ID from second_level_cats l2)
May not be entirely optimal but it seems to work. If there's only a relatively low number of rows and you're only ever going to go to three levels then it should suffice.
Let's say I have two tables, "Parent" and "Child". Parent-to-Child is a many:many relationship, implemented through a standard cross-referencing table.
I want to find all records of Parent that are referenced by ALL members of a given set of Child using SQL (in particular MS SQL Server's T-SQL; 2005 syntax is acceptable).
For example let's say I have:
List item
Parent Alice
Parent Bob
Child Charlie references Alice, Bob
Child David references Alice
Child Eve references Bob
My goals are:
If I have Children Charlie, I want the result set to include Alice and Bob
If I have Children Charlie and David, I want the result set to include Alice and NOT Bob.
If I have Children Charlie, David, and Eve, I want the result set to include nobody.
Relying on a numerical trick (where the number of parent-child links = the number of children, that parent is linked to all children):
SELECT Parent.ParentID, COUNT(*)
FROM Parent
INNER JOIN ChildParent
ON ChildParent.ParentID = Parent.ParentID
INNER JOIN Child
ON ChildParent.ChildID = Child.ChildID
WHERE <ChildFilterCriteria>
GROUP BY Parent.ParentID
HAVING COUNT(*) = (
SELECT COUNT(Child.ChildID)
FROM Child WHERE <ChildFilterCriteria>
)
Here's an answer.
SQL query: Simulating an "AND" over several rows instead of sub-querying
And here's a specific application of that to this problem.
SELECT * FROM Parents
WHERE ParentId in
(
SELECT ParentId FROM ChildParent
WHERE ChildId in
(
SELECT ChildId FROM Child
WHERE ChildName in ('Charlie', 'David')
)
GROUP BY ParentId
HAVING COUNT(*) = 2
)
( I guess where you said "Child Eve references Eve" you meant "Child Eve references Bob", right?)
I think I've got it... looks ugly... the secret is the double negation... that is, everyone for which it's true,, is the same as not anyone for which is false... (ok, I have troubles with my english, but I guess you understand what I mean)
select * from parent
parent_id name
--------------------------------------- --------------------------------------------------
1 alice
2 bob
select * from child
child_id name
--------------------------------------- --------------------------------------------------
1 charlie
2 david
3 eve
select * from parent_child
parent_id child_id
--------------------------------------- ---------------------------------------
1 1
2 1
1 2
2 3
select * from parent p
where not exists(
select * from child c
where
c.child_id in ( 1, 2, 3 ) and
not exists(
select * from parent_child pc where
pc.child_id = c.child_id and
pc.parent_id = p.parent_id
)
)
--when child list = ( 1 )
parent_id name
--------------------------------------- --------------------------------------------------
1 alice
2 bob
--when child list = ( 1, 2 )
parent_id name
--------------------------------------- --------------------------------------------------
1 alice
--when child list = ( 1, 2, 3 )
parent_id name
--------------------------------------- --------------------------------------------------
well, I hope it helps...