How to find level of employee position using RECURSIVE COMMON TABLE EXPRESSION - sql

;with Ranked(Empid,Mngrid,Empnm,RN,level) As
(select Empid,Mngrid ,Empnm ,row_number() over (order by Empid)AS RN ,
0 as level from dbo.EmpMngr),
AnchorRanked(Empid,Mngrid,Empnm,RN,level)
AS(select Empid,Mngrid,Empnm,RN ,level from Ranked ),
RecurRanked(Empid,Mngrid,Empnm,RN,level)
AS(select Empid,Mngrid,Empnm,RN,level from AnchorRanked
Union All
select Ranked.Empid,Ranked.Mngrid,Ranked.Empnm,Ranked.RN,Ranked.level + 1
from Ranked
inner join RecurRanked
on Ranked.Empid = RecurRanked.Empid AND
Ranked.RN = RecurRanked.RN+1)
select Empid,Empnm,level from RecurRanked

This may help CTE Example of a simple hierarchy

;WITH OrgChart AS
(
SELECT EmpID, EmpNm, [Level] = 0
FROM EmpMngr
WHERE MngrID IS NULL
UNION ALL
SELECT e.EmpID, e.EmpNm, [Level] + 1
FROM OrgChart o
JOIN EmpMngr e ON (e.MngrID = o.EmpID)
)
SELECT EmpID, EmpNm, [Level] FROM OrgChart

Related

Rotation of table1 rows for given date range based on each employee

I need one small help in understanding of repeating in cycle the rows from one table (RuleSetting) to my result.
Few points to remember:
For now I have only 2 setting value for emplloyee 4536, it could be 4 or 5 as well..
SettingId rotating after every 7 days because in RuleSetting Table I have mentioned WeekOffFrequency = 7 for employee 4536
Instead of 99 it should repeat between settingIds 1,2
It should work for all employee
SQL Fiddler link:
http://sqlfiddle.com/#!18/9eecb/92011
I have attached the screenshot so that I can explain my requirement more clearly.
Note:
Records/Rows in RuleSetting table is not fixed... employee can have nth number of settings...
but RuleSetting.WeekOffFrequency value will be always same for employee
Please ask me if thre is any doubt...
I implemented it as follows.
Here
ExpandRuleSettings(add): Expand rows of RuleSettings to number of WeekOffFrequency each SettingId of employee.
NumberOfRotate(add): Get number of rotate (number of all SettingId of each employee).
EmployeeDates(change): Allocate rotated Id for date each employee.
EmployeeWeekOffFrequency(change): Join the tables EmployeeDates and ExpandRuleSettings on matching EmployeeID and Id.
DECLARE #sdate date = '2020-09-10', #edate date = '2020-09-28'
;with dates_CTE (RosterDate) as (
select #sdate
Union ALL
select DATEADD(day, 1, RosterDate)
from dates_CTE
where RosterDate < #edate
),
-- Expand rows of RuleSettings to number of WeekOffFrequency each SettingId of employee.
ExpandRuleSettingsWork as (
SELECT 0 AS IdForSettingId, SettingId, EmployeeID, ShiftPattern, WeekOffFrequency FROM #RuleSettings
UNION ALL
SELECT IdForSettingId+1, SettingId, EmployeeID, ShiftPattern, WeekOffFrequency FROM ExpandRuleSettingsWork
WHERE IdForSettingId+1 < WeekOffFrequency
),
ExpandRuleSettings as (
SELECT
ROW_NUMBER() OVER(PARTITION BY EmployeeID ORDER BY SettingId,IdForSettingId)-1 AS Id,
* FROM ExpandRuleSettingsWork
),
-- SELECT * FROM ExpandRuleSettings ORDER BY EmployeeID,SettingId,Id
-- Get number of rotate (number of all SettingId of each employee).
NumberOfRotate as (
SELECT EmployeeID, COUNT(EmployeeID) AS Num FROM ExpandRuleSettings GROUP BY EmployeeID
),
-- SELECT * FROM NumberOfRotate
EmployeeDates as (
SELECT
(ROW_NUMBER() over (partition by EmployeeId order by RosterDate) - 1) % Num as Id,
EmployeeID,
RosterDate
FROM dates_CTE
CROSS APPLY NumberOfRotate
)
--select * from EmployeeDates
--order by EmployeeID , RosterDate
,
EmployeeWeekOffFrequency as (
SELECT ed.*, er.SettingId
FROM EmployeeDates as ed INNER JOIN ExpandRuleSettings as er
ON ed.EmployeeID=er.EmployeeID AND
ed.Id = er.Id
)
SELECT * FROM EmployeeWeekOffFrequency
I have slightly modified your query and added RuleSettingsModified and NewRotateSettingId.
Please check if that works fine for you.
RuleSettingsModified AS
(
SELECT Row_number() OVER (PARTITION BY rs.EmployeeID ORDER BY (SELECT NULL)) rn, rs.EmployeeID, rs.WeekOffFrequency
FROM #RuleSettings rs
)
(Id / (SELECT DISTINCT WeekOffFrequency FROM #RuleSettings WHERE EmployeeID = ed.EmployeeID))%(SELECT count(1) FROM RuleSettingsModified rsm WHERE rsm.EmployeeID = ed.EmployeeID)+1 AS NewRotateSettingId
;with dates_CTE (RosterDate) as (
select #sdate
Union ALL
select DATEADD(day, 1, RosterDate)
from dates_CTE
where RosterDate < #edate
),
EmployeeDates as (
SELECT
ROW_NUMBER() over (partition by EmployeeId order by RosterDate) - 1 as Id,
EmployeeID,
RosterDate
FROM dates_CTE
CROSS APPLY (SELECT DISTINCT EmployeeId from #RuleSettings)as cs
),
RuleSettingsModified AS
(
SELECT Row_number() OVER (PARTITION BY rs.EmployeeID ORDER BY (SELECT NULL)) rn, rs.EmployeeID, rs.WeekOffFrequency
FROM #RuleSettings rs
)
,
EmployeeWeekOffFrequency as (
SELECT *,
Id / (SELECT DISTINCT WeekOffFrequency from #RuleSettings where EmployeeID = ed.EmployeeID)+ 1 as WeekOffFrequency,
(
CASE
WHEN
(
Id / (SELECT DISTINCT WeekOffFrequency FROM #RuleSettings WHERE EmployeeID = ed.EmployeeID)
)
<
(SELECT count(1) FROM #RuleSettings WHERE EmployeeID = ed.EmployeeID)
THEN
(
SELECT SettingId FROM #RuleSettings WHERE EmployeeID = ed.EmployeeID
AND SettingId = ((Id / (SELECT DISTINCT WeekOffFrequency FROM #RuleSettings
WHERE EmployeeID = ed.EmployeeID )) + 1 )
)
ELSE 99
END
) as RotateSettingId,
(Id / (SELECT DISTINCT WeekOffFrequency FROM #RuleSettings WHERE EmployeeID = ed.EmployeeID))%(SELECT count(1) FROM RuleSettingsModified rsm WHERE rsm.EmployeeID = ed.EmployeeID)+1 AS NewRotateSettingId
from EmployeeDates as ed
)
SELECT * FROM EmployeeWeekOffFrequency
Please see complete code here.

SQL Self Recursive Join with Grouping

In SQL Server, I have the following table:
Name New_Name
---------------
A B
B C
C D
G H
H I
Z B
I want to create a new table that links all the names that are related to a single new groupID
GroupID Name
-------------
1 A
1 B
1 C
1 D
1 Z
2 G
2 H
2 I
I'm a bit stuck on this can can't figure out how to do it apart from a bunch of joins. But I would like to do it properly.
Edited the question to allow grouping from two different start points, A and Z into one group.
Since you've changed the question, I'm updating the answer. Please note that answer is same in sense of logical structure. All it does differently is that instead of going from G to I in calculating levels, answer now calculates from I to G.
Working demo link
;with cte as
(
select
t1.Name as Name, row_number() over (order by t1.Name) r,
t1.New_Name as New_Name,
1 as level
from yt t1 left join yt t2
on t1.New_Name=t2.name
where t2.name is null
union all
select
yt.Name as Name, r,
yt.New_Name as New_Name,
c.level+1 as level
from cte c join yt
on yt.New_Name=c.Name
),
cte2 as
(
select r as group_id, Name from cte
union
select c1.r as group_id, c1.New_name as Name from cte c1
where level = (select min(level) from cte c2 where c2.r=c1.r)
)
select * from cte2;
Below is old answer.
You can try below CTE based query:
create table yt (Name varchar(10), New_Name varchar(10));
insert into yt values
('A','B'),
('B','C'),
('C','D'),
('G','H'),
('H','I');
;with cte as
(
select
t1.Name as Name, row_number() over (order by t1.Name) r,
t1.New_Name as New_Name,
1 as level
from yt t1 left join yt t2
on t1.Name=t2.New_name
where t2.new_name is null
union all
select
yt.Name as Name, r,
yt.New_Name as New_Name,
c.level+1 as level
from cte c join yt
on yt.Name=c.New_Name
),
cte2 as
(
select r as group_id, Name from cte
union
select c1.r as group_id, c1.New_name as Name from cte c1
where level = (select max(level) from cte c2 where c2.r=c1.r)
)
select * from cte2;
see working demo
Little bit complex but working.
DECLARE #T TABLE (Name VARCHAR(2), New_Name VARCHAR(2))
INSERT INTO #T
VALUES
('A','B'),
('B','C'),
('C','D'),
('G','H'),
('H','I'),
('Z','B')
;WITH CTE AS
(
SELECT * , RN = ROW_NUMBER() OVER(ORDER BY Name) FROM #T
)
,CTE2 AS (SELECT T1.RN, T1.Name Name1, T1.New_Name New_Name1,
X.Name Name2, X.New_Name New_Name2,
FLAG = CASE WHEN X.Name IS NULL THEN 1 ELSE 0 END
FROM CTE T1
OUTER APPLY (SELECT * FROM CTE T2 WHERE T2.RN > T1.RN
AND (T2.Name IN (T1.Name , T1.New_Name)
OR T2.New_Name IN (T1.Name , T1.New_Name)
)) AS X
)
,CTE3 AS (SELECT *,
GroupID = ROW_NUMBER() OVER (ORDER BY RN) -
ROW_NUMBER() OVER (PARTITION BY Flag ORDER BY RN) +1
FROM CTE2
)
SELECT
DISTINCT GroupID, Name
FROM
(SELECT * FROM CTE3 WHERE Name2 IS NOT NULL) SRC
UNPIVOT ( Name FOR COL IN ([Name1], [New_Name1], [Name2], [New_Name2])) UNPVT
Result
GroupID Name
-------------------- ----
1 A
1 B
1 C
1 D
1 Z
2 G
2 H
2 I

SQL Server 2012 - Convert a From-To columned table into a numbered sequence

I am trying to write a SQL query in SQL Server 2012 to convert a table that has 2 columns FROM and TO into a table that has a numerical sequence of the described route taken if you logically follow the from-to direction. I have been struggling for hours on this and any hints would be greatly appreciated. Thanks
EXAMPLE:
To answer this I'll have to assume either a sort order or a "base record". I.e. I need to know which record should be the starting point. In this example I have simply hardcoded the value of the base record.
WITH RecursiveCTE AS(
SELECT
[FROM], [TO],
1 AS SequenceNo
FROM InputTable
WHERE [FROM] = 'B' --Hardcoded value to select a base record
UNION ALL
SELECT
t.[FROM], t.[TO],
SequenceNo + 1 AS SequenceNo
FROM RecursiveCTE e
INNER JOIN InputTable t ON e.[TO] = t.[FROM]
)
--Get all records except the last one via the Recursive CTE
SELECT
[FROM] AS [Node],
SequenceNo
FROM RecursiveCTE
UNION ALL
--Get the last record in a separate query
SELECT TOP 1
[TO] AS [Node],
SequenceNo + 1 AS SequenceNo
FROM RecursiveCTE
WHERE SequenceNo = (SELECT MAX(SequenceNo) FROM RecursiveCTE)
The query uses a recursive CTE to get all records except the last one. The last record is added by the UNION ALL statement at the end.
Assuming no cycles, you can use a recursive cte:
with cte as (
select e.from, e.to, 1 as lev
from example e
where not exists (select 1 from example e2 where e2.to = e.from)
union all
select e.from, e.to, cte.lev + 1
from cte join
example e
on cte.to = e.from
)
select e.from, lev
from cte;
I assumed that you have random data on [from] or [to] column - (may be there more data in [from] column not in [to] column and vise-versa, so
if there is no problem with making an order using [from] and [to] column use the below query
with dataTest as
(
select [From], [to] , ROW_NUMBER() over(order by [From], [to]) lvl from InputTable e
)
,dataD as
(
select [From] node,(lvl*2)-1 lvl from dataTest
union all
select [To] node,(lvl*2) lvl from dataTest
)
,lastD as
(
select node, lvl, ROW_NUMBER() over(partition by node order by lvl) rn from dataD
)
select node, ROW_NUMBER() over(order by lvl) squence from lastD where rn=1 order by lvl
if your data is typically as you set in the sample you can use
with dataTest as
(
select [From], [to] ,1 lvl from InputTable e
where not exists (select 1 from InputTable e2 where e2.[to] = e.[from])
union all
select e.[from], e.[to], dataTest.lvl + 1
from dataTest join
InputTable e on dataTest.[to] = e.[from]
)
,dataD as
(
select [From] node,(lvl*2)-1 lvl from dataTest
union all
select [To] node,(lvl*2) lvl from dataTest
)
,lastD as
(
select node, lvl, ROW_NUMBER() over(partition by node order by lvl) rn
from dataD
)
select node, ROW_NUMBER() over(order by lvl) squence from lastD where rn=1 order by lvl
Hope these help you or give you an idea.

Select rows with same ID/email but different value in other table

Select rows with same ID/email but different value in other table
I have two tables: person and email, now there are mail addresses that have the same value, and persons/ID with different values.
Can anyone tell how to write an SQL query for this? I have tried but I can't figure it out. I have found some answers but then it is always finding the match in the same table
Like this
Table_person. ​​Table_email
1​​​ email#persoon1
2​​​ email#persoon2
3​​​ email#persoon3
4​​​ email#persoon1
5​​​ email#persoon5
6​​​ email#persoon2
The output should be
Table_person​​ Table_email
1​​​ email#persoon1
4​​​ email#persoon1
2​​​ email#persoon2
6​​​ email#persoon2
Using a common table expression with row_number()
;with cte as (
select *
, rn = row_number() over (partition by email order by person_id)
from email e
)
select *
from cte
where exists (
select 1
from cte i
where i.email = cte.email
and rn > 1
)
or using exists()
select *
from email e
where exists (
select 1
from email i
where i.email = e.email
and i.person_id <> e.person_id
)
rextester demo: http://rextester.com/JHFEF82373
Hope it will helps you
;with cte(Table_person,​​Table_email)
AS
(
SELECT 1​​​,'email#persoon1' UNION ALL
SELECT 2​​​,'email#persoon2' UNION ALL
SELECT 3​​​,'email#persoon3' UNION ALL
SELECT 4​​​,'email#persoon1' UNION ALL
SELECT 5​​​,'email#persoon5' UNION ALL
SELECT 6​​​,'email#persoon2'
)
,Cte2
AS
(
SELECT Table_person,​​Table_email From
(
Select Table_person,​​Table_email,ROW_NUMBER()OVER(Partition by Table_email Order By Table_person )Seq
from cte
)dt WHERE dt.Seq>1
)
,Final
AS
(
SELECT Table_person,​​Table_email From
(
Select Table_person,​​Table_email,ROW_NUMBER()OVER(Partition by Table_email Order By Table_email )Seq2
from cte
)dt
where dt.Seq2>1
Union ALL
SELECT Table_person,​​Table_email From cte2
)
SELECt Table_person,​​Table_email from Final

Find the highest grandparent on self-referencing table SQL Server

I have this table in SQL Server:
Parent Child
1 2
89 7
2 3
10 5
3 4
I need to build a recursive Stored Procedure that finds the maximum ascendant of any child.
For example: If I want to find the maximum ascendant of 4, it should return 1 because:
4 is the child of 3.
3 is the child of 2.
2 is the child of 1.
So I can find the ultimate parent.
A perfect job for recursive CTE:
;WITH
cte1 AS
( -- Recursively build the relationship tree
SELECT Parent
, Child
, AscendentLevel = 1
FROM my_table
UNION ALL
SELECT t.Parent
, cte1.Child
, AscendentLevel = cte1.AscendentLevel + 1
FROM cte1
INNER JOIN my_table t ON t.Child = cte1.Parent
),
cte2 AS
( -- Now find the ultimate parent
SELECT Parent
, Child
, rn = ROW_NUMBER() OVER (PARTITION BY Child ORDER BY AscendentLevel DESC)
FROM cte1
)
SELECT *
FROM cte2
WHERE rn = 1
OPTION (MAXRECURSION 0)
WITH CTE_myTable AS (
SELECT Id, ParentId, NULL As RootParent, 1 As Lvl
FROM dbo.myTable
UNION ALL
SELECT a.id, b.ParentId, a.ParentId As RootParent, Lvl + 1
FROM CTE_myTable AS a INNER JOIN
dbo.myTable AS b ON a.ParentId = b.Id
)
, CTE_myTable_RN AS (
SELECT Id, RootParent, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Lvl DESC) RN
FROM CTE_myTable
)
, JoinParentChild As (
SELECT Id, ISNULL(RootParent, Id) As RootParent
FROM CTE_myTable_RN
WHERE RN = 1
)
SELECT TOP(100) PERCENT JoinParentChild.Id, JoinParentChild.RootParent
FROM JoinParentChild
ORDER BY JoinParentChild.Id