TSQL Manager Hierarchy - sql-server

I need to be able to show all my managers in a hierarchy in different columns. I don't know how many levels there will be.
Example: Employee – ManagerOfEmployee - TheBigBoss
I have tried the below but cant get it to work the way I want it.
I need the results to look like this:
Level1Column Level2Column Level3Column
------------------------------------------
1 2 3
Code:
CREATE TABLE #tblHRData
(
Emplid INT,
ReportsToEmplid INT
)
INSERT INTO #tblHRData (Emplid, ReportsToEmplid)
VALUES (1, 2), (2, 3)
;WITH CTE AS
(
SELECT
Emplid,
ReportsToEmplid,
1 AS level
FROM
#tblHRData
WHERE
Emplid = 1
UNION ALL
SELECT
child.Emplid,
child.ReportsToEmplid,
level + 1
FROM
#tblHRData child
JOIN
CTE parent ON child.ReportsToEmplid = parent.Emplid
)
SELECT *
FROM CTE;

With an unknown depth you will have to go the dynamic SQL route. But such cases tend to have a maximal depth. As your columns will have computable names, you can try this:
I enhanced your table a bit:
CREATE TABLE #tblHRData
(
Emplid INT,
ReportsToEmplid INT,
Descr VARCHAR(100)
)
INSERT INTO #tblHRData (Emplid, ReportsToEmplid, Descr)
VALUES (1, 2, 'lvl 3.2.1') --boss is 2
,(2, 3, 'lvl 3.2') --boss is 3
,(3,null, 'big boss')--big boss reports to no one
,(4, 3, 'lvl 3.4') --one more 2nd lvl
,(5, 4, 'lvl 3.4.5') --below 4
,(6, 4, 'lvl 3.4.6') --another one below 4
-- And I changed the recursive CTE to start off with the big boss and to build a sort string on the fly. In this case this is limited to 3 digits. You'll have to widen this with Emplids exceeding 999:
;WITH CTE AS
(
SELECT
Emplid,
ReportsToEmplid,
Descr,
0 AS EmpLvl,
CAST(REPLACE(STR(Emplid,3),' ','0') AS VARCHAR(MAX)) AS SortOrder
FROM
#tblHRData
WHERE
ReportsToEmplid IS NULL --start with the big boss
UNION ALL
SELECT
child.Emplid,
child.ReportsToEmplid,
child.Descr,
parent.EmpLvl + 1,
parent.SortOrder + REPLACE(STR(child.Emplid,3),' ','0')
FROM
#tblHRData child
JOIN
CTE parent ON child.ReportsToEmplid = parent.Emplid
)
SELECT Emplid,
SortOrder,
MAX(CASE WHEN EmpLvl=0 THEN Descr END) AS BossDescr,
MAX(CASE WHEN EmpLvl=1 THEN Descr END) AS Lvl1Descr,
MAX(CASE WHEN EmpLvl=2 THEN Descr END) AS Lvl2Descr,
MAX(CASE WHEN EmpLvl=3 THEN Descr END) AS Lvl3Descr,
MAX(CASE WHEN EmpLvl=4 THEN Descr END) AS Lvl4Descr,
MAX(CASE WHEN EmpLvl=5 THEN Descr END) AS Lvl5Descr
--add as many as you need and add some more to be future safe
FROM CTE
GROUP BY EmpLvl,Emplid,SortOrder
ORDER BY SortOrder;
GO
DROP TABLE #tblHRData
The result
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| Emplid | SortOrder | BossDescr | Lvl1Descr | Lvl2Descr | Lvl3Descr | Lvl4Descr | Lvl5Descr |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 3 | 003 | big boss | NULL | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 2 | 003002 | NULL | lvl 3.2 | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 1 | 003002001 | NULL | NULL | lvl 3.2.1 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 4 | 003004 | NULL | lvl 3.4 | NULL | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 5 | 003004005 | NULL | NULL | lvl 3.4.5 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 6 | 003004006 | NULL | NULL | lvl 3.4.6 | NULL | NULL | NULL |
+--------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
Some remarks:
- I use the conditional aggregation as PIVOT approach. With just one column this can be done with PIVOT() too.
- The SortOrder is important to be created in the recursion. It is kind of the path to the entry and will allow you to order your result-set.
- This path must allow alphanumerical sorting. Therefore I concatenate padded strings.

Related

How to select all PK's (column 1) where the MAX(ISNULL(value, 0)) in column 3 grouped by a value in column 2?

I couldn't find an answer on my question since all questions similar to this one aren't using a nullable int in the max value and getting 1 column out of it.
My table is as follows:
| ContractId | ContractNumber | ContractVersion |
+------------+----------------+-----------------+
| 1 | 11 | NULL |
| 2 | 11 | 1 |
| 3 | 11 | 2 |
| 4 | 11 | 3 | --get this one
| 5 | 24 | NULL |
| 6 | 24 | 1 | --get this one
| 7 | 75 | NULL | --get this one
The first version is NULL and all following versions get a number starting with 1.
So now I only want to get the rows of the latest contracts (as shown in the comments behind the rows).
So for each ContractNumber I want to select the ContractId from the latest ContractVersion.
The MAX() function wont work since it's a nullable int.
So I was thinking to use the ISNULL(ContractVersion, 0) in combination with the MAX() function, but I wouldn't know how.
I tried the following code:
SELECT
ContractNumber,
MAX(ISNULL(ContractVersion, 0))
FROM
Contracts
GROUP BY
ContractNumber
...which returned all of the latest version numbers combined with the ContractNumber, but I need the ContractId. When I add ContractId in the SELECT and the GROUP BY, I'm getting all the versions again.
The result should be:
| ContractId |
+------------+
| 4 |
| 6 |
| 7 |
It's just a simple application of ROW_NUMBER() when you're wanting to select rows based on Min/Max:
declare #t table (ContractId int, ContractNumber int, ContractVersion int)
insert into #t(ContractId,ContractNumber,ContractVersion) values
(1,11,NULL ),
(2,11, 1 ),
(3,11, 2 ),
(4,11, 3 ),
(5,24,NULL ),
(6,24, 1 ),
(7,75,NULL )
;With Numbered as (
select *,ROW_NUMBER() OVER (
PARTITION BY ContractNumber
order by ContractVersion desc) rn
from #t
)
select
*
from
Numbered
where rn = 1
this will work:
select ContractId,max(rank),ContractNumber from(select *,rank() over(partition by
ContractVersion order by nvl(ContractVersion,0)) desc ) rank from tablename) group by
ContractId,max(rank),ContractNumber;

How do I return zero counts in GROUP BY statement

I would like my COUNT and GROUP BY statements to return a zero value
The below is a simplified version of what I'm trying to do:
SELECT DISTINCT
,r.Team
,r.Peron_ID
,'Person Status' AS 'VariableType'
, CASE
WHEN p.STATUS= 'Y' THEN 'Good'
WHEN p.STATUS = 'N' THEN 'Bad'
WHEN p.STATUS IS NULL OR p.STATUS NOT IN ('Y','N') THEN 'Invalid'
END AS VariableA
,NULL AS VariableB
into #TC
from Test r
INNER JOIN Person p ON r.RECORD_NUMBER = p.RECORD_NUMBER
Select
,t.Team
,t.Person_ID
,VariableType
,variableA
,ISNULL(count(t.Person_ID),0) as Count
from #TC t
Group by t.team, t.Person_ID, variabletype, VariableA
For a simple example, in the following table Person1 has a number for each VariableA, however Person2 doesn't have an entry for any of them:
+--------+-------------+---------------+-----------+-------+
| t.Team | t.Person_ID | VariableType | VariableA | Count |
|--------+-------------+---------------+-----------+-------+
| Team1 | Person1 | Person Status | Good | 2 |
| Team1 | Person1 | Person Status | Bad | 3 |
| Team1 | Person1 | Person Status | Invalid | 2 |
| Team1 | Person2 | Person Status | NULL | NULL |
+--------+-------------+---------------+-----------+-------+
How should I go about getting Person2's Good, Bad and Invalid counts showing up as 0?
Please take the above as a sort of pseudo-code, I have paraphrased and simplified my actual code, which is company-specific and relies on lots of casts and cases, so the syntax may not be spot on.
Thanks for the response Ross. Person2 doesn't have his P.STATUS recorded at all. Which is why he's just showing as NULL, I think anyway.
My expected (or just hoped-for) result would like this:
+--------+-------------+---------------+-----------+-------+
| t.Team | t.Person_ID | VariableType | VariableA | Count |
+--------+-------------+---------------+-----------+-------+
| Team1 | Person1 | Person Status | Good | 2 |
| Team1 | Person1 | Person Status | Bad | 3 |
| Team1 | Person1 | Person Status | Invalid | 2 |
| Team1 | Person2 | Person Status | Good | 0 |
| Team1 | Person2 | Person Status | Bad | 0 |
| Team1 | Person2 | Person Status | Invalid | 0 |
+--------+-------------+---------------+-----------+-------+
https://learn.microsoft.com/en-us/previous-versions/sql/sql-server-2005/ms175997(v=sql.90)
COUNT always returns an int data type value. COUNT_BIG always returns
a bigint data type value.
Count always not null value
multiple result your status
DECLARE #a TABLE (a int, status varchar(255))
INSERT INTO #a VALUES (1, 'Good')
SELECT
a.a,
x.Status,
SUM((CASE WHEN a.Status =x.Status THEN 1 ELSE 0 END)) Cnt
FROM #a a
CROSS JOIN
(
SELECT 'Good' UNION ALL
SELECT 'Bad' UNION ALL
SELECT 'Invalid'
) as x(Status)
GROUP BY
a.a,
x.Status
If you want the 3 unique values for variableA to show up for each combination of person/team/variabletype, you need to create an inline view with them and then use that in a CROSS JOIN:
CREATE TABLE #Test(
[RECORD_NUMBER] [int] NOT NULL,
[Team] [nvarchar](50) NULL,
[Person_ID] [int] NULL
);
CREATE TABLE #Person(
[RECORD_NUMBER] [int] NOT NULL,
[STATUS] [nvarchar](1) NULL
);
SELECT r.Team
,r.Person_ID
,'Person Status' AS 'VariableType'
, CASE
WHEN p.STATUS= 'Y' THEN 'Good'
WHEN p.STATUS = 'N' THEN 'Bad'
WHEN p.STATUS IS NULL and p.RECORD_NUMBER IS NULL THEN NULL
WHEN p.STATUS IS NULL OR p.STATUS NOT IN ('Y','N') THEN 'Invalid'
END AS VariableA
,NULL AS VariableB
into #TC
from Test r
LEFT JOIN Person p ON r.RECORD_NUMBER = p.RECORD_NUMBER;
SELECT
t.Team,
t.Person_ID,
t.VariableType,
VarValues.VariableA,
SUM((CASE WHEN t.VariableA = VarValues.VariableA THEN 1 ELSE 0 END)) Cnt
FROM #TC t
CROSS JOIN
(
SELECT 'Good' UNION ALL
SELECT 'Bad' UNION ALL
SELECT 'Invalid'
) as VarValues(VariableA)
GROUP BY
t.Team,
t.Person_ID,
t.VariableType,
VarValues.VariableA
ORDER BY t.Person_ID, VarValues.VariableA;
DROP TABLE #TC;
DROP TABLE #Test;
DROP TABLE #Person;

calculate days with gaps

table:
+-----------+--------------+------------+------------+
| RequestID | RequestStaus | StartDate | EndDate |
+-----------+--------------+------------+------------+
| 1 | pending | 9/1/2015 | 10/2/2015 |
| 1 | in progress | 10/2/2015 | 10/20/2015 |
| 1 | completed | 10/20/2015 | 11/3/2015 |
| 1 | reopened | 11/3/2015 | null |
| 2 | pending | 9/5/2015 | 9/7/2015 |
| 2 | in progress | 9/7/2015 | 9/25/2015 |
| 2 | completed | 9/25/2015 | 10/7/2015 |
| 2 | reopened | 10/10/2015 | 10/16/2015 |
| 2 | completed | 10/16/2015 | null |
+-----------+--------------+------------+------------+
I would like to calculate the days opened but exclude the days between completed and reopened. For example, RequestID 1, the days opened will be (11/3/2015 - 9/1/2015) + (GetDate() - 11/3/2015), for request 2, the total days will be (10/7/2015 - 9/5/2015) + ( 10/16/2015 - 10/10/2015).
The result I want will be something like:
+-----------+-------------------------------+
| RequestID | DaysOpened |
+-----------+-------------------------------+
| 1 | 63 + (getdate() - 11/3/2015) |
| 2 | 38 |
+-----------+-------------------------------+
How do I approach this problem? thank you!
Tested. Works well. :)
Note:
1) I suppose the required result = (FirstCompleteEndDate - PendingStartDate)+(Sum of all the Reopen duration)
2) So I used the self joins. Table b provides the exact completed record which immediately follows the in process record for each RequestID. Table c provides Sum of all the Reopen duration.
--create tbl structure
create table #test (RequestID int, RequestStatus varchar(20), StartDate date, EndDate date)
go
--insert sample data
insert #test
select 1,'pending','09/01/2015','10/2/2015'
union all
select 1,'in progress','10/2/2015','10/20/2015'
union all
select 1,'completed','10/20/2015','11/3/2015'
union all
select 1,'reopened','11/3/2015',null
union all
select 2,'pending','09/05/2015','9/7/2015'
union all
select 2,'in progress','09/07/2015','9/25/2015'
union all
select 2,'completed','9/25/2015','10/7/2015'
union all
select 2,'reopened','10/10/2015','10/16/2015'
union all
select 2, 'completed','10/16/2015','11/12/2015'
union all
select 2,'reopened','11/20/2015',null
select * from #test
--below is solution
select a.RequestID, a.Startdate as [PendingStartDate], b.enddate as [FirstCompleteEndDate], c.startdate as [LatestReopenStartDate],
datediff(day,a.startdate,b.enddate)+c.ReopenDays as [days] from #test a
join (
select *, row_number()over(partition by RequestID,RequestStatus order by StartDate) as rid from #test
) as b
on a.RequestID = b.RequestID
join (
select distinct RequestID, RequestStatus, max(StartDate)over(partition by RequestID,RequestStatus) as StartDate,
Sum(Case when enddate is null then datediff(day,startdate,getdate())
when enddate is not null then datediff(day,startdate,enddate)
end)over(partition by RequestID,RequestStatus) as [ReopenDays]
from #test
where RequestStatus = 'reopened'
) as c
on b.RequestID = c.RequestID
where a.RequestStatus ='pending' and b.RequestStatus = 'completed' and b.rid = 1
Result:

SQL statement - join based on date

I need to write a statement joining two tables based on dates.
Table 1 contains time recording entries.
+----+-----------+--------+---------------+
| ID | Date | UserID | DESC |
+----+-----------+--------+---------------+
| 1 | 1.10.2010 | 5 | did some work |
| 2 | 1.10.2011 | 5 | did more work |
| 3 | 1.10.2012 | 4 | me too |
| 4 | 1.11.2012 | 4 | me too |
+----+-----------+--------+---------------+
Table 2 contains the position of each user in the company. The ValidFrom date is the date at which the user has been or will be promoted.
+----+-----------+--------+------------+
| ID | ValidFrom | UserID | Pos |
+----+-----------+--------+------------+
| 1 | 1.10.2009 | 5 | PM |
| 2 | 1.5.2010 | 5 | Senior PM |
| 3 | 1.10.2010 | 4 | Consultant |
+----+-----------+--------+------------+
I need a query which outputs table one with one added column which is the position of the user at the time the entry has been made. (the Date column)
All date fileds are of type date.
I hope someone can help. I tried a lot but don't get it working.
Try this using a subselect in the where clause:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE TimeRecord
(
ID INT,
[Date] Date,
UserID INT,
Description VARCHAR(50)
)
INSERT INTO TimeRecord
VALUES (1,'2010-01-10',5,'did some work'),
(2, '2011-01-10',5,'did more work'),
(3, '2012-01-10', 4, 'me too'),
(4, '2012-11-01',4,'me too')
CREATE TABLE UserPosition
(
ID Int,
ValidFrom Date,
UserId INT,
Pos VARCHAR(50)
)
INSERT INTO UserPosition
VALUES (1, '2009-01-10', 5, 'PM'),
(2, '2010-05-01', 5, 'Senior PM'),
(3, '2010-01-10', 4, 'Consultant ')
Query 1:
SELECT TR.ID,
TR.[Date],
TR.UserId,
TR.Description,
UP.Pos
FROM TimeRecord TR
INNER JOIN UserPosition UP
ON UP.UserId = TR.UserId
WHERE UP.ValidFrom = (SELECT MAX(ValidFrom)
FROM UserPosition UP2
WHERE UP2.UserId = UP.UserID AND
UP2.ValidFrom <= TR.[Date])
Results:
| ID | Date | UserId | Description | Pos |
|----|------------|--------|---------------|-------------|
| 1 | 2010-01-10 | 5 | did some work | PM |
| 2 | 2011-01-10 | 5 | did more work | Senior PM |
| 3 | 2012-01-10 | 4 | me too | Consultant |
| 4 | 2012-11-01 | 4 | me too | Consultant |
You can do it using OUTER APPLY:
SELECT ID, [Date], UserID, [DESC], x.Pos
FROM table1 AS t1
OUTER APPLY (
SELECT TOP 1 Pos
FROM table2 AS t2
WHERE t2.UserID = t1.UserID AND t2.ValidFrom <= t1.[Date]
ORDER BY t2.ValidFrom DESC) AS x(Pos)
For every row of table1 OUTER APPLY operation fetches all table2 rows of the same user that have a ValidFrom date that is older or the same as [Date]. These rows are sorted in descending order and the most recent of these is finally returned.
Note: If no match is found by the OUTER APPLY sub-query then a NULL value is returned, meaning that no valid position exists in table2 for the corresponding record in table1.
Demo here
This works by using a rank function and subquery. I tested it with some sample data.
select sub.ID,sub.Date,sub.UserID,sub.Description,sub.Position
from(
select rank() over(partition by t1.userID order by t2.validfrom desc)
as 'rank', t1.ID as'ID',t1.Date as'Date',t1.UserID as'UserID',t1.Descr
as'Description',t2.pos as'Position', t2.validfrom as 'validfrom'
from temployee t1 inner join jobs t2 on -- replace join tables with your own table names
t1.UserID=t2.UserID
) as sub
where rank=1
This query would work
select t1.*,t2.pos from Table1 t1 left outer join Table2 t2 on
t1.Date=t2.Date and t1.UserID=t2.UserID

Create sql query to inherits value from nearest parent row

I have a self-referenced table called Units that has a "BossId" column refers to manager person .
There is business rule to determine the unit's Boss as described below:
1-The unit has its own BossId. (there is no more work)
2-The BossId is null. in this case we refer to the most nearest parent that has bossId value
i wanna create an efficient SQL view that all unit and their boss is specified according to the mentioned rules
below is the structure of my unit table:
CREATE TABLE [dbo].[Unit](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ParentId] [int] NULL,
[BossId] [int] NULL,
Here is the sample data:
INSERT INTO Units (ID, ParentID, BossId) VALUES (1, NULL, 1000)
INSERT INTO Units (ID, ParentID, BossId) VALUES (2, 1, NULL)
INSERT INTO Units (ID, ParentID, BossId) VALUES (3, 2, NULL)
INSERT INTO Units (ID, ParentID, BossId) VALUES (4, 1, 3000)
INSERT INTO Units (ID, ParentID, BossId) VALUES (5, 4, NULL)
Selecting the data as follows:
Select ID,ParentId,BossId from Units
result would be:
+----+-------+----------+
| ID | ParentId| BossId|
+----+-------+----------+
| 1 | NULL | 1000 |
| 2 | 1 | NULL |
| 3 | 2 | NULL |
| 4 | 1 | 3000 |
| 5 | 4 | NULL |
+----+-------+----------+
I need some view to produce something like this:
+----+-------+----------+
| ID | ParentId| BossId|
+----+-------+----------+
| 1 | NULL | 1000 |
| 2 | 1 | 1000 |
| 3 | 2 | 1000 |
| 4 | 1 | 3000 |
| 5 | 4 | 3000 |
+----+-------+----------+
So all unit's boss id is specified according to the rule
With _cte (ParentId, Id, BossId)
As
(
Select ParentId, Id, BossId
From Units
Union All
Select U.parentId, U.Id, c.BossId
From Units As U
Join _cte As c
On u.ParentId = c.Id
)
Select Id, ParentId, Max(BossId) As BossId
From _cte
Where BossId Is Not Null
Group
By Id, ParentId
Order
By Id, ParentId
Produces
Id ParentId BossId
----------- ----------- -----------
1 NULL 1000
2 1 1000
3 2 1000
4 1 3000
5 4 3000

Resources