Getting all managers of the user by using CTE on SQL server - sql

I try to get all top managers of the user.
For example.
Dan king(Specialist) --> Adam Lion(Product Manager) --> Reina Ken (Head of IT) --> Michael Lorren (Director)--> Pat Deep (CEO)
I want to show top managers of Dan King on one column the below:
Adam
Reina
Michael
Pat
NOTE:
Get all employees under manager with CTE
I used this link and I decided to take users of manager.
I tried to convert my code according the sample code but it didn't work:
;WITH OS_MANAGERS AS
(
SELECT USERID, MANAGERUSERID
from OSMANAGERS
), Manager AS
(
SELECT USERID, MANAGERUSERID
from OS_MANAGERS
where [userID]='mbalasubrama'
union all
SELECT B.USERID, B.MANAGERUSERID
from Manager A
inner join OS_MANAGERS B ON A.MANAGERUSERID= B.USERID
)
select A.USERID , a.MANAGERUSERID
from OS_MANAGERS A
left join Manager B on A.USERID=B.USERID
where A.[userID]='mbalasubrama'
only I see the manager of user, other top ones don't come
RESULT:

Your code seems a bit too complicated... Try this:
DECLARE #tbl TABLE(USERID INT,MANAGERUSERID INT,Name VARCHAR(100));
INSERT INTO #tbl VALUES
(1,NULL,'CEO Pat')
,(2,1,'Director Michael')
,(3,2,'HIT Reina')
,(4,3,'PM Adam')
,(5,4,'Dan King');
WITH ManagerRecursive AS
(
SELECT USERID, MANAGERUSERID,Name
from #tbl
where [userID]=5
union all
SELECT tbl.USERID, tbl.MANAGERUSERID,tbl.Name
from ManagerRecursive AS MR
INNER JOIN #tbl AS tbl ON tbl.USERID=MR.MANAGERUSERID
)
SELECT * FROM ManagerRecursive ;
The result
USERID MANAGERUSERID Name
5 4 Dan King
4 3 PM Adam
3 2 HIT Reina
2 1 Director Michael
1 NULL CEO Pat

You can see my code:
WITH ManagerRecursive AS
(
SELECT [USERID],[MANAGERUSERID]
FROM [EBA].[dbo].[OSMANAGERS]
where [userID]='dking'
union all
SELECT tbl.USERID, tbl.MANAGERUSERID
from ManagerRecursive AS MR
INNER JOIN [EBA].[dbo].[OSMANAGERS] AS tbl ON tbl.USERID=MR.MANAGERUSERID
)
SELECT * FROM ManagerRecursive ;
Result:
Userid manageruserid
dking tkir
tkir skara

Related

Get column names and data to rows in SQL

I have a table with basic employee details as below:
Table: tblEmployees
EmpID Name Contact Sex
100 John 55555 M
200 Kate 44444 F
300 Sam 88888 M
I would like to get my query result as follows of a particular employee where EmpID = 200
Col1 Col2
EmpID 200
Name Kate
Sex F
You can use cross apply:
select t.*
from employees e
cross apply (values
('empid', cast(empid as varchar(100))),
('name', name),
('sex', sex)
) t(attr, value)
where e.empid = 200
Presumably, empid is a number, so explicit casting is needed (otherwise sql server will try to cast the name and sex to numbers, which will fail).
Demo on DB Fiddle:
attr | value
:---- | :----
empid | 200
name | Kate
sex | F
Or a less sophisticated solution involving 3 UNIONs, assuming the field names are predetermined in advance. This might perform better on large tables.
If you have performance issues, analyze the execution plan and make sure indexes are utilized optimally.
Since you are only looking for one particular employee at a time:
SELECT 'empid', convert(varchar(12), EmpID)
FROM tblEmployees
WHERE EmpID = 200
UNION ALL
SELECT 'name', name
FROM tblEmployees
WHERE EmpID = 200
UNION ALL
SELECT 'sex', sex
FROM tblEmployees
WHERE EmpID = 200
The first line does convert(varchar(12) under the assumption that EmpID is an int field.
Another option is with a little XML
Full Disclosure: Not as performant as GMB's CROSS APPLY (+1) or UNPIVOT. BUT it will dynamically unpivot virtually any row, table, view or ad-hoc query without actually using dynamic SQL.
Example
Declare #YourTable Table ([EmpID] varchar(50),[Name] varchar(50),[Contact] varchar(50),[Sex] varchar(50)) Insert Into #YourTable Values
(100,'John',55555,'M')
,(200,'Kate',44444,'F')
,(300,'Sam',88888,'M')
Select A.EmpID
,C.*
From #YourTable A
Cross Apply ( values (convert(xml,(select a.* for XML Raw ))) ) B(XMLData)
Cross Apply (
Select Item = xAttr.value('local-name(.)', 'varchar(100)')
,Value = xAttr.value('.','varchar(max)')
From XMLData.nodes('//#*') xNode(xAttr)
Where xAttr.value('local-name(.)', 'varchar(100)') not in ('EmpID','Other','Columns2Exclude')
) C
Returns
EmpID Item Value
100 Name John
100 Contact 55555
100 Sex M
200 Name Kate
200 Contact 44444
200 Sex F
300 Name Sam
300 Contact 88888
300 Sex M
EDIT - If Interested Here a TVF approach
Select A.EmpID
,B.*
From #YourTable A
Cross Apply [dbo].[tvf-XML-UnPivot-Row]((Select A.* for XML RAW)) B
The TVF
CREATE FUNCTION [dbo].[tvf-XML-UnPivot-Row](#XML xml)
Returns Table
As
Return (
Select Item = xAttr.value('local-name(.)', 'varchar(100)')
,Value = xAttr.value('.','varchar(max)')
From #XML.nodes('//#*') xNode(xAttr)
)

T-SQL Execute Recursion for Each Value in Multiple Columns

I have a dataset with two columns. A person in the first column may be in the span of control of a person in the second column (i.e. everyone is in Michael's span of control, Dwight & Stanley are in Jim's span of control):
source_id source target_id target
1 Michael Scott 5 Kelly Kapoor
3 Dwight Schrute 2 Jim Halpert
4 Stanley Hudson 2 Jim Halpert
2 Jim Halpert 5 Kelly Kapoor
I have a table that lists each person and their supervisor:
person_id person supervisor_id supervisor
1 Michael Scott 0 None
2 Jim Halpert 1 Michael Scott
3 Dwight Schrute 2 Jim Halpert
4 Stanley Hudson 2 Jim Halpert
6 Ryan Howard 1 Michael Scott
5 Kelly Kapoor 6 Ryan Howard
I have a block of code that uses recursion to find a single person's span of control from the preceding table:
with anchor as
(
select person_id, supervisor_id from table where unique_id = #ID
union all
select a.person_id, a.supervisor_id
from table a
inner join Anchor b ON b.person_id = a.supervisor_id
)
select a.person_id
from anchor a
This block of code can be turned into a stored procedure or a function. Running it for Jim (for example), returns:
person_id
3 (Dwight Schrute)
4 (Stanley Hudson)
How do I compare each value in the initial dataset (from both the source and target columns) to the values in the column returned by the preceding block of code? In other words, for each row in source, I need to check if that name is within the span of control of target. In addition, for each row in target, I need to check if that name is within the span of control of source.
Desired End Result:
source_id source target_id target isEitherPersonInOther'sSOC
1 Michael Scott 5 Kelly Kapoor Yes
3 Dwight Schrute 2 Jim Halpert Yes
4 Stanley Hudson 2 Jim Halpert Yes
2 Jim Halpert 5 Kelly Kapoor No
I know iterations are bad (i.e. running the stored procedure for each row with a cursor or while loop). I also know cross apply and a function together may work, but I have been unable to figure my way through that.
Thank you for any insight you all may have!
What it looks like you need is to take your recursive CTE and output ID pairs into a temp table. I'm picturing something like this:
DECLARE #id int
DECLARE cx CURSOR FOR
SELECT person_id FROM PersonSupervisor
CREATE TABLE #tmpPS (Supervisor int, personInChain int)
OPEN cx
FETCH NEXT FROM cx
INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
with anchor as
(
select person_id, supervisor_id from table where unique_id = #ID
union all
select a.person_id, a.supervisor_id
from table a
inner join Anchor b ON b.person_id = a.supervisor_id
)
INSERT INTO #tmpPS
select #id, a.person_id
from anchor a
FETCH NEXT FROM cx
INTO #id
END
Close cx
Deallocate cx
This creates a table of all relationships, recursively expanded. Then you can output whether any given person is either above or below a given other person with this subquery. Add it to whatever query outputs your base grid:
SELECT
SourceId,
Source,
TargetId,
Target,
CASE
WHEN EXISTS (SELECT 1 FROM #tmpPS WHERE Supervisor = SourceId and PersonInChain = TargetId)
THEN 'Yes'
WHEN EXISTS (SELECT 1 FROM #tmpPS WHERE Supervisor = TargetId and PersonInChain = SourceId)
THEN 'Yes'
ELSE 'No'
END as [isEitherPersonInOther'sSOC]
FROM ....
This also implies a version of this where you can separate the relationships out - If the first query, the TargetId is a subordinate of the SourceId. If the second query, then TargetId is a superior to SourceId.
There has to be a better way to do this but this is definitely one way to go about it -- note the code creates permanent tables to use in a function:
create table dbo.[source]
(
source_id int,
[source] nvarchar(500),
target_id int,
[target] nvarchar(500)
)
insert into [source]
select 1, 'Michael Scott', 5, 'Kelly Kapoor'
union all select 3,'Dwight Schrute',2,'Jim Halpert'
union all select 4 ,'Stanley Hudson',2,'Jim Halpert'
union all select 2 ,'Jim Halpert',5,'Kelly Kapoor'
create table dbo.supervisors
(
person_id int,
person nvarchar(500),
supervisor_id int,
supervisor nvarchar(500)
)
insert into dbo.supervisors
select 1,'Michael Scott', 0,'None'
union all select 2,'Jim Halpert',1,'Michael Scott'
union all select 3,'Dwight Schrute',2,'Jim Halpert'
union all select 4,'Stanley Hudson',2,'Jim Halpert'
union all select 6,'Ryan Howard',1,'Michael Scott'
union all select 5 ,'Kelly Kapoor',6,'Ryan Howard'
go
create function dbo.fn_isinspanofcontrol
(
#sourceid int,
#targetid int
)
RETURNS varchar(1)
as
begin
declare #retVal varchar(1)
declare #tbl table
(
person_id int
)
;with anchor as
(
select person_id, supervisor_id from supervisors where person_id = #sourceid
union all
select a.person_id, a.supervisor_id
from supervisors a
inner join Anchor b ON b.person_id = a.supervisor_id
)
insert into #tbl
select a.person_id
from anchor a
where
a.person_id = #targetid
;with anchor as
(
select person_id, supervisor_id from supervisors where person_id = #targetid
union all
select a.person_id, a.supervisor_id
from supervisors a
inner join Anchor b ON b.person_id = a.supervisor_id
)
insert into #tbl
select a.person_id
from anchor a
where
a.person_id = #sourceid
if exists( select 1
from #tbl
)
begin
set #retVal = 'Y'
end
else
begin
set #retVal = 'N'
end
return #retVal
end
select
*, dbo.fn_isinspanofcontrol(source_id,target_id)
from [source]

SQL View Assign Alternating Data from Another Table

I cannot explain this thoroughly with words as English is not my native language so I will try visual presentation. First, I have a table that looks like this let's call it tblPerson:
tblPerson
ID Name
1 John
2 Paul
3 James
Then I have another table tblPhones:
tblPhones
ID Mobile
1 123456
2 654321
Now for my question, is it possible to create a view that will look like this:
Person-Phone
Name Mobile
John 123456
Paul 654321
James 123456
What I want to display is a list of person, and use the tblPhones to assign the mobile column but assign it alternately. So if a new person is added lets say Mark. The view would look like this:
Person-Phone
Name Mobile
John 123456
Paul 654321
James 123456
Mark 654321
How can I query this?
Try this:
SELECT Name, Mobile
FROM (
SELECT Name,
ROW_NUMBER() OVER (ORDER BY ID) As rn
FROM tblPerson) AS t1
INNER JOIN (
SELECT Mobile,
ROW_NUMBER() OVER (ORDER BY ID) AS rn,
COUNT(*) OVER () AS cnt
FROM tblPhones
) AS t2 ON (t1.rn - 1) % cnt + 1 = t2.rn
Demo here
#Giorgos beat me to it, but here's my version. You don't need the row_number window function assuming the IDs are contiguous (if they're not, you do :).
CREATE TABLE #tblPerson (ID INT,Name VARCHAR(5));
CREATE TABLE #tblPhones (ID INT, Mobile VARCHAR(6));
INSERT INTO #tblPerson(ID, Name) VALUES( 1, 'John'),( 2, 'Paul'),( 3, 'James');
INSERT INTO #tblPhones(ID, Mobile) VALUES( 1,'123456'),( 2,'654321');
SELECT
Name, Mobile
FROM #tblPerson
JOIN #tblPhones ON #tblPhones.ID = ((#tblPerson.ID-1) % (SELECT COUNT(*) FROM #tblPhones) +1)
ORDER BY #tblPerson.ID

Query to List all hierarchical parents and siblings and their childrens, but not list own childrens

I've a basic SQL table with a simple heirarchial connection between each rows. That is there is a ParentID for Every rows and using that its connecting with another row. Its as follows
AccountID | AccountName | ParentID
---------------------------------------
1 Mathew 0
2 Philip 1
3 John 2
4 Susan 2
5 Anita 1
6 Aimy 1
7 Elsa 3
8 Anna 7
.............................
.................................
45 Kristoff 8
Hope the structure is clear
But my requirement of listng these is a little weird. That is when we pass an AccountID, it should list all its parents and siblings and siblings childrens. But it never list any child of that AccountID to any level. I can explain that in little more detail with a picture. Sorry for the clarity of the picture.. mine is an old phone cam..
When we pass the AccountID 4, it should list all Parents and its siblings, but it should not list 4,6,7,8,9,10. That means that account and any of it childrens should be avoid in the result (Based on the picture tree elements). Hope the explanation is clear.
If I've got it right and you need to output whole table except 4 and all of it's descendants then try this recursive query:
WITH CT AS
(
SELECT * FROM T WHERE AccountID=4
UNION ALL
SELECT T.* FROM T
JOIN CT ON T.ParentID = CT.AccountId
)
SELECT * FROM T WHERE AccountID
NOT IN (SELECT AccountID FROM CT)
SQLFiddle demo
Answering to the question in the comment:
So it will not traverse to the top. It only traverse to specified
account. For example if I pass 4 as first parameter and 2 as second
parameter, the result should be these values 2,5,11,12
You should start from the ID=2 and travel to the bottom exclude ID=4 so you cut whole subtree after ID=4:
WITH CT AS
(
SELECT * FROM T WHERE AccountID=2
UNION ALL
SELECT T.* FROM T
JOIN CT ON T.ParentID = CT.AccountId
WHERE T.AccountId<>4
)
SELECT * FROM CT
Try this:
;with cte as
(select accountid,parentid, 0 as level from tbl
where parentid = 0
union all
select t.accountid,t.parentid,(level+1) from
cte c inner join tbl t on c.accountid= t.parentid
)
select * from cte
where level < (select level from cte where accountid = #accountid)
When you pass in the parameter #accountid this will return the accountid values of all nodes on levels before that of the parameter.
If you want to return everything on the same level as input except input itself, you can change the where clause to;
where level <=(select level from cte where accountid= #accountid )
and accountid <> #accountid
In your example, if #accountid = 4, this will return the values 1,2,3 (ancestors) as well as 5,13,14 (siblings).
Does this return what you are after?
declare #AccountID int
set #AccountID = 4
;with parents
as (
select AccountID, AccountName, ParentID
from Account
where AccountID = (select ParentID from Account Where AccountID = #AccountID)
union all
select A.AccountID, A.AccountName, A.ParentID
from Account as A
join parents as P
on P.ParentID = A.AccountID
),
children
as (
select AccountID, AccountName, ParentID
from parents
union all
select A.AccountID, A.AccountName, A.ParentID
from Account as A
join children as C
on C.AccountID = A.ParentID
where A.AccountID <> #AccountID
)
select distinct AccountID, AccountName, ParentID
from children
order by AccountID
For me it sounds like you want to go up in the tree. So considering this test data
DECLARE #tbl TABLE(AccountID INT,AccountName VARCHAR(100),ParentID INT)
INSERT INTO #tbl
VALUES
(1,'Mathew',0),
(2,'Philip',1),
(3,'John',2),
(4,'Susan',2),
(5,'Anita',1),
(6,'Aimy',1),
(7,'Elsa',3),
(8,'Anna',7)
The I would write a query like this:
DECLARE #AcountID INT=4
;WITH CTE
AS
(
SELECT
tbl.AccountID,
tbl.AccountName,
tbl.ParentID
FROM
#tbl AS tbl
WHERE
tbl.AccountID=#AcountID
UNION ALL
SELECT
tbl.AccountID,
tbl.AccountName,
tbl.ParentID
FROM
#tbl AS tbl
JOIN CTE
ON CTE.ParentID=tbl.AccountID
)
SELECT
*
FROM
CTE
WHERE
NOT CTE.AccountID=#AcountID
This will return a result like this:
2 Philip 1
1 Mathew 0

Need query to select direct and indirect customerID aliases

I need a query that will return all related alias id's from either column. Shown here are some alias customer ids, among thousands of other rows. If the input parameter to a query is id=7, I need a query that would return 5 rows (1,5,7,10,22). That is because they are all aliases of one-another. For example, 22 and 10 are indirect aliases of 7.
CustomerAlias
--------------------------
AliasCuID AliasCuID2
--------------------------
1 5
1 7
5 7
10 5
22 1
Here is an excerpt from the customer table.
Customer
----------------------------------
CuID CuFirstName CuLastName
----------------------------------
1 Mike Jones
2 Fred Smith
3 Jack Jackson
4 Emily Simpson
5 Mike Jones
6 Beth Smith
7 Mike jones
8 Jason Robard
9 Emilie Jiklonmie
10 Michael jones
11 Mark Lansby
12 Scotty Slash
13 Emilie Jiklonmy
22 mike jones
I've been able to come close, but I cannot seem to select the indirectly related aliases correctly. Given this query:
SELECT DISTINCT Customer.CuID, Customer.CuFirstName, Customer.CuLastName
FROM Customer WHERE
(Customer.CuID = 7) OR (Customer.CuID IN
(SELECT AliasCuID2
FROM CustomerAlias AS CustomerAlias_2
WHERE (AliasCuID = 7))) OR (Customer.CuID IN
(SELECT AliasCuID
FROM CustomerAlias AS CustomerAlias_1
WHERE (AliasCuID2 = 7)))
Returns 3 out of 5 of the desired ids of course. This lacks the indirectly related aliased id of 10 and 22 in the result rows.
1 Mike Jones
5 Mike Jones
7 Mike jones
* Based on suggestions below, I am trying a CTE hierarchical query.
I have this now after following some suggestions. It works for some, as long as the records in the table reference enough immediate ids. But, if the query uses id=10, then it still comes up short, just by the nature of the data.
DECLARE #id INT
SET #id = 10;
DECLARE #tmp TABLE ( a1 INT, a2 INT, Lev INT );
WITH Results (AliasCuID, AliasCuID2, [Level]) AS (
SELECT AliasCuID,
AliasCuID2,
0 as [Level]
FROM CustomerAlias
WHERE AliasCuID = #id OR AliasCuID2 = #id
UNION ALL
-- Recursive step
SELECT a.AliasCuID,
a.AliasCuID2,
r.[Level] + 1 AS [Level]
FROM CustomerAlias a
INNER JOIN Results r ON a.AliasCuID = r.AliasCuID2 )
INSERT INTO #tmp
SELECT * FROM Results;
WITH Results3 (AliasCuID, AliasCuID2, [Level]) AS (
SELECT AliasCuID,
AliasCuID2,
0 as [Level]
FROM CustomerAlias
WHERE AliasCuID = #id OR AliasCuID2 = #id
UNION ALL
-- Recursive step
SELECT a.AliasCuID,
a.AliasCuID2,
r.[Level] + 1 AS [Level]
FROM CustomerAlias a
INNER JOIN Results3 r ON a.AliasCuID2 = r.AliasCuID )
INSERT INTO #tmp
SELECT * FROM Results3;
SELECT DISTINCT a1 AS id FROM #tmp
UNION ALL
SELECT DISTINCT a2 AS id FROM #tmp
ORDER BY id
Note that this is a simplified the query to just give a list of related ids.
---
id
---
5
5
7
10
But, it is still unable to pull in ids 1 and 22.
This is not an easy problem to solve unless you have some idea of the depth of your search (https://stackoverflow.com/a/7569520/1803682) - which it looks like you do not - and take a brute force approach to it.
Assuming you do not know the depth you will need to write a stored proc. I followed this approach for a nearly identical problem: https://dba.stackexchange.com/questions/7147/find-highest-level-of-a-hierarchical-field-with-vs-without-ctes/7161#7161
UPDATE
If you don't care about the chain of how the alias's were created - I would run a script recursively to make them all refer to a single (master?) record. Then you can easily do the search and it will be quick - not a solution if you care about how the alias's get traversed though.
I created a SQL Fiddle for SQL Server 2012. Please let me know if you can or cannot access it.
My thought here was that you'd want to just keep checking the left and right branches recursively, separately. This logic probably falls apart if the relationships bounce between left and right. You could set up a third CTE to reference the first two, but joining on left to right and right to left, but ain't nobody got time for that.
The code is below as well.
CREATE TABLE CustomerAlias
(
AliasCuID INT,
AliasCuID2 INT
)
GO
INSERT INTO CustomerAlias
SELECT 1,5
UNION SELECT 1, 7
UNION SELECT 5, 7
UNION SELECT 10, 5
UNION SELECT 22, 1
GO
DECLARE #Value INT
SET #Value = 7
; WITH LeftAlias AS
(
SELECT AliasCuID
, AliasCuID2
FROM CustomerAlias
WHERE AliasCuID2 = #Value
UNION ALL
SELECT a.AliasCuID
, a.AliasCuID2
FROM CustomerAlias a
JOIN LeftAlias b
ON a.AliasCuID = b.AliasCuID2
)
, RightAlias AS
(
SELECT AliasCuID
, AliasCuID2
FROM CustomerAlias
WHERE AliasCuID = #Value
UNION ALL
SELECT a.AliasCuID
, a.AliasCuID2
FROM CustomerAlias a
JOIN LeftAlias b
ON a.AliasCuID2 = b.AliasCuID
)
SELECT DISTINCT A
FROM
(
SELECT A = AliasCuID
FROM LeftAlias
UNION ALL
SELECT A = AliasCuID2
FROM LeftAlias
UNION ALL
SELECT A = AliasCuID
FROM RightAlias
UNION ALL
SELECT A = AliasCuID2
FROM RightAlias
) s
ORDER BY A