Full recursive employee-boss relation in SQL Server - sql

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

Related

How to write insert function or query

I have three tables which are very extensive, but I will give only a small version:
employee_position :
ID
position
1
Programmer
2
Administrator
3
Analyst
employee:
ID
name
1
Adam Smith
2
Sam Jones
3
Barbra Streisand
4
Dorothy Brown
employee_functions:
ID
employee_id
employee_position
1
1
3
2
3
1
Assuming what I need: if there is no employee in the employee_functions table [from the employee table] with an assigned position, then all positions in the employee_positions table should be assigned to him in this table.
I admit that I am not very fluent in SQL, so I would ask for help, can it be done with one query or can I do some function?
The final result for the given data (employee_functions):
ID
employee_id
employee_position
1
1
3
2
3
1
3
2
1
4
2
2
5
2
3
6
4
1
7
4
2
8
4
3
here is one way :
select employee_id ,employee_position from employee_functions f
union all
select e.id, p.id from employees e
cross join employee_position p
where e.id not in (select employee_id from employee_functions)
You can use insert . . . select, filtering out the employees who already have functions:
insert into employee_functions (employee_id, position_id)
select e.id, p.id
from employee e cross join
employee_positions ep
where not exists (select 1
from employee_functions ef
where ef.employee_id = e.id
);
insert into employee_functions (employee_id, position_id)
select e.id, p.id
from employee e cross join
employee_positions ep
where not exists (select 1
from employee_functions ef
where ef.employee_id = e.id
);

Select from table 1 unless there is a relationship in 2 other tables

I need to query a name(s) from the Officials table, but exclude that name if the person has the day blocked.
For example, if Sam has blocked 8/21/2021 and 9/11/2021, he should not be selected if the corresponding dates are selected from the Games table. Sam should show up if 9/18/2021 is selected, however. I have 3 tables:
Officials tbl
RefId Name
---------------------
1 Jack
2 Sam
3 Jane
Games tbl Blocks tbl
GameId GameDate BlockId RefId BlockDate
------------------------- ----------------------
1 8/21/2021 1 2 8/21/2021
2 9/11/2021 2 2 9/11/2021
3 9/18/2021 3 3 8/21/2021
Desired Output
----------------------------------
If Game 1 is selected: Jack
If Game 2 is selected: Jack and Jane
If Game 3 is selected: Jack, Sam and Jane
The only 2 tables that are related are the Officials table and Blocks table, with the RefId. I need to compare the BlockDate of Blocks table to GameDate of Games table. I have tried some sql language and this below is obviously not correct, but I'm looking for a way to accomplish what I want to do:
#GameDate datetime,
Select c.Id, c.Name
From Officials c
Where In c.Id And Blocks.BlockDate <> Games.GameDate)
You can do it with NOT EXISTS:
SELECT o.*
FROM Officials o
WHERE NOT EXISTS (
SELECT 1
FROM Blocks b INNER JOIN Games g
ON g.GameDate = b.BlockDate
WHERE b.RefId = o.RefId AND g.GameId = ?
);
See the demo.

Selecting objects that are associated with similar datasets

I'm trying to select all company rows from a [Company] table that share with at least one other company, the same number of employees (from an [Employee] table that has a CompanyId column), where each group of respective employees share the same set of LocationIds (a column in the [Employee] table) and in the same proportion.
So, for instance, two companies with three employees each that have the locationIds 1,2, and 2, would be selected by this query.
[Employee]
EmployeeId | CompanyId | LocationId |
========================================
1 | 1 | 1
2 | 1 | 2
3 | 1 | 2
4 | 2 | 1
5 | 2 | 2
6 | 2 | 2
7 | 3 | 3
[Company]
CompanyId |
============
1 |
2 |
3 |
Returns the CompanyIds:
======================
1
2
CompanyIds 1 and 2 are selected because they share in common with at least one other company: 1. the number of employees (3 employees); and 2. the number/proportion of LocationIds associated with those employees (1 employee has LocationId 1 and 2 employees have LocationId 2).
So far I think I want to use a HAVING COUNT(?) > 1 statement, but I'm having trouble working out the details. Does anyone have any suggestions?
This is ugly, but the only way I can think of to do it:
;with CTE as (
select c.Id,
(
select e.Location, count(e.Id) [EmployeeCount]
from Employee e
where e.IdCompany=c.Id
group by e.Location
order by e.Location
for xml auto
) LocationEmployeeData
from Company c
)
select c.Id
from Company c
join (
select x.LocationEmployeeData, count(x.Id) [CompanyCount]
from CTE x
group by x.LocationEmployeeData
having count(x.Id) >= 2
) y on y.LocationEmployeeData = (select LocationEmployeeData from CTE where Id = c.Id)
See fiddle: http://www.sqlfiddle.com/#!6/6bc16/5
It works by encoding the Employee count per Location data (multiple rows) into an xml string for each Company.
The CTE code on its own:
select c.Id,
(
select e.Location, count(e.Id) [EmployeeCount]
from Employee e
where e.IdCompany=c.Id
group by e.Location
order by e.Location
for xml auto
) LocationEmployeeData
from Company c
Produces data like:
Id LocationEmployeeData
1 <e Location="1" EmployeeCount="2"/><e Location="2" EmployeeCount="1"/>
2 <e Location="1" EmployeeCount="2"/><e Location="2" EmployeeCount="1"/>
3 <e Location="3" EmployeeCount="1"/>
Then it compares companies based on this string (rather than trying to ascertain whether multiple rows match, etc).
An alternative solution could look like this. However it also requires performance testing in advance (I don't feel quite confident with <> type join).
with List as
(
select
IdCompany,
Location,
row_number() over (partition by IdCompany order by Location) as RowId,
count(1) over (partition by IdCompany) as LocCount
from
Employee
)
select
A.IdCompany
from List as A
inner join List as B on A.IdCompany <> B.IdCompany
and A.RowID = B.RowID
and A.LocCount = B.LocCount
group by
A.IdCompany, A.LocCount
having
sum(case when A.Location = B.Location then 1 else 0 end) = A.LocCount
Related fiddle: http://sqlfiddle.com/#!6/d9f2e/1

Show COUNT of each possible grade for an employee, showing zero when there are no grade entries

I have only one table available. I want to show the grade and the count of the number of times an employee has that grade recorded, but it must show a 0 for the grade if there are no records for that employee. I know how to do this using left join when two tables are present, but I only have 1 table.
How is this possible?
For example:
TABLE
empID | dept | grade
1 | 11 | a
2 | 11 | a
3 | 11 | b
1 | 22 | c
2 | 22 | f
3 | 22 | d
1 | 33 | a
2 | 33 | a
3 | 33 | a
If I run SELECT grade, count(grade) from table where empID = 1 Group by grade;, for example, it ends up printing out only grades the employee got and the count. Now I want to also print out the 0s for grades the employee did not have.
i think you're asking for this?
SQL> select e.grade, count(e2.empid)
2 from (select distinct grade from e) e
3 left outer join e e2
4 on e2.grade = e.grade
5 and e2.empid = 1
6 group by e.grade
7 order by grade;
G COUNT(E2.EMPID)
- ---------------
a 2
b 0
c 1
d 0
f 0
or as you have no rows with "e" grade then if you have a lookup table called grade:
SQL> select * from grade;
G
-
a
b
c
d
e
f
SQL> select e.grade, count(e2.empid)
2 from grade emp
3 left outer join emp e2
4 on e2.grade = e.grade
5 and e2.empid = 1
6 group by e.grade
7 order by grade;
G COUNT(E2.EMPID)
- ---------------
a 2
b 0
c 1
d 0
e 0
f 0
Let's say your query to select a value is:
select value from tbl;
You can ensure a 0 is returned if there are no rows in tbl t:
select nvl(t.value, 0) value
from dual d
left join tbl t on 1=1;
Sounds like you want the NVL function. With NVL, you can conditionally return an alternate value if the value is null. See the documentation.
So, if you had the following...
SELECT fooName, fooNumber FROM foo
and these were your results
fooName, fooNumber
Blah, 1
Asdf, null
Qwer, 3
poiu, null
you could rewrite the query like this...
SELECT fooName, NVL(fooNumber, 0) FROM foo
and your results would now be...
fooName, fooNumber
Blah, 1
Asdf, 0
Qwer, 3
poiu, 0
http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions105.htm

self referencing table with child table

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.