Getting the primacy function for an emolyee - sql

I have this following query to get the primacy for an employee :
with
employeeScopeFunctions as (
select e.employeeId,
es.FunctionId,
ef.Label,
c.CompanyName
,es.SortOrder
from employee e
LEFT JOIN employee_scope es on es.employeeId = e.employeeId
LEFT JOIN employee_function ef on es.FunctionId = ef.FunctionId
LEFT JOIN Company c ON es.CompanyId = c.ID
WHERE e.EmployeeId=54
)
SELECT DISTINCT esf.EmployeeId, esf.FunctionId
,STUFF((SELECT ',' + CompanyName
FROM employeeScopeFunctions es
WHERE esf.EmployeeId=es.EmployeeId AND esf.FunctionId=es.FunctionId
FOR XML PATH('')),1,1,'') AS Companies
,SUM(esf.SortOrder) as sumOrder
FROM employeeScopeFunctions esf
GROUP BY esf.EmployeeId,esf.FunctionId
My output is like below :
EmployeeId FunctionId Label CompanyName sumOrder
54 39 Director C1,C2,X5 224
54 273 Group Chief Executive Officer X6,F6 66
54 897 Group Regional Chief Executive Officer VY,G7 130
What I want is to get primacy (primary,secondary, tertiary) to each function like below :
EmployeeId FunctionId Label CompanyName sumOrder primacy
54 39 Director C1,C2,X5 224 tertiary
54 273 Group Chief Executive Officer X6,F6 66 primary
54 897 Group Regional Chief Executive Officer VY,G7 130 secondary
The function having the minimal sumOrder will be the primary function and so on.

You can use a ROW_NUMBER() ordered by the SUM() result, then a CASE to display your custom string value.
;with employeeScopeFunctions as
(
SELECT
e.employeeId,
es.FunctionId,
ef.Label,
c.CompanyName,
es.SortOrder
FROM
employee e
LEFT JOIN employee_scope es on es.employeeId = e.employeeId
LEFT JOIN employee_function ef on es.FunctionId = ef.FunctionId
LEFT JOIN Company c ON es.CompanyId = c.ID
WHERE
e.EmployeeId = 54
),
RankingBySum AS
(
SELECT
esf.EmployeeId,
esf.FunctionId,
STUFF (
(
SELECT ',' + CompanyName
FROM employeeScopeFunctions es
WHERE esf.EmployeeId=es.EmployeeId AND esf.FunctionId=es.FunctionId
FOR XML PATH('')
)
, 1,1,'') AS Companies,
SUM(esf.SortOrder) as sumOrder,
-- Add this ranking here
SumRanking = ROW_NUMBER() OVER (PARTITION BY esf.EmployeeId ORDER BY SUM(esf.SortOrder) ASC)
FROM
employeeScopeFunctions esf
GROUP BY
esf.EmployeeId,
esf.FunctionId
)
SELECT
R.EmployeeId,
R.FunctionId,
R.Companies,
R.sumOrder,
-- Display your primacy condition here
primacy = CASE R.SumRanking
WHEN 1 THEN 'primary'
WHEN 2 THEN 'seconday'
WHEN 3 THEN 'tertiary' END
FROM
RankingBySum AS R

Related

How to get the cumulative sum of children up its parents?

So I have written a query to get the cumulative sum of children but I think partition sum has error as its totalling for the parent that is not part of the children.
My fiddle is http://sqlfiddle.com/#!15/88828/1
I have dont a running total but thats wrong. I want the siblings total to a child and child total back to its parent.So basically cumulative total of a child up the tree.
expected output
parent_child_tree id name dimensionvalueid level order_sequence volume cummulative_total
A1 1 A1 (null) 0 1 20 840
-----A1:1 2 A1:1 1 1 1_1 (null) 820
----------A1:1:1 3 A1:1:1 2 2 1_1_2 20 820
-----------A1:1:1:1 4 A1:1:1:1 3 3 1_1_2_3 300 800
-----------A1:1:1:2 5 A1:1:1:2. 3 3 1_1_2_3 500 500
B1 6 B1 (null) 0 6 200 300
-----B1:2 8 B1:2 6 1 6_6 (null) null
-----B1:1 7 B1:1 6 1 6_6 (null) 100
----------B1:2:1 9 B1:2:1 8 2 6_6_8 100 100
To get totals for tree nodes you need to generate hierarchy tree for every node in a subquery like this
SELECT
d.*,
v.volume,
(
WITH RECURSIVE cte AS (
SELECT
dd.id AS branch_id,
dd.id
FROM dimensionvalue dd
WHERE dd.id = d.id
UNION ALL
SELECT
cte.branch_id,
dd.id
FROM dimensionvalue dd
JOIN cte ON dd.dimensionvalueid = cte.id
)
SELECT SUM(v.volume)
FROM cte
JOIN valuation v ON v.dimensionvalueid = cte.id
GROUP BY cte.branch_id
) AS totals
FROM dimensionvalue d
LEFT JOIN valuation v ON v.dimensionvalueid = d.id
ORDER BY d.name;
If you really need all those "decoration" columns that you generate in your query for each tree node than you can combine your recursive CTE hierarchy with subquery for totals calculation like this
WITH RECURSIVE hierarchy AS (
SELECT
d.id,
d.name,
d.dimensionvalueid,
0 AS level,
CAST(d.id AS varchar(50)) AS order_sequence
FROM dimensionvalue d
WHERE d.dimensionvalueid IS NULL
UNION ALL
SELECT
e.id,
e.name,
e.dimensionvalueid,
hierarchy.level + 1 AS level,
CAST(hierarchy.order_sequence || '_' || CAST(hierarchy.id AS VARCHAR(50)) AS VARCHAR(50)) AS order_sequence
FROM hierarchy
JOIN dimensionvalue e ON e.dimensionvalueid = hierarchy.id
)
SELECT
RIGHT('-----------', h.level * 5) || h.name || ' ' AS parent_child_tree,
h.*,
v.volume,
(
WITH RECURSIVE cte AS (
SELECT
dd.id AS branch_id,
dd.id
FROM dimensionvalue dd
WHERE dd.id = h.id
UNION ALL
SELECT
cte.branch_id,
dd.id
FROM dimensionvalue dd
JOIN cte ON dd.dimensionvalueid = cte.id
)
SELECT SUM(v.volume)
FROM cte
JOIN valuation v ON v.dimensionvalueid = cte.id
GROUP BY cte.branch_id
) AS totals
FROM hierarchy h
LEFT JOIN valuation v ON v.dimensionvalueid = h.id
ORDER BY h.name
You can check a working demo here

Recursive query compute parent values

I have 2 tables Persons and Sales. In Person there is relation between child and parent, I want to compute 20 percent of parent values with the following condition
Persons
Id | ParentId | Name
1 NULL Tom
2 1 Jake
3 2 Kate
4 3 Neil
Sales
PersonId | Sale
4 500
I want to get result like this
Id | ParentId | Name | Sale
1 Null Tom 100 <-- (500*20)/100 left 400
2 1 Jake 80 <-- (400*20)/100 left 320
3 2 Kate 64 <-- (320*20)/100 left 256
4 3 Neil 256 <-- (320*80)/100
I wrote this query but it does not give appropriate result
;WITH cte_persons
AS
(
SELECT p.Id, p.ParentId, p.Name, s.Price FROM Persons AS p
INNER JOIN Sales AS s ON s.PersonId = p.Id
UNION ALL
SELECT p.Id, p.ParentId, p.Name, CAST((c.Price - (c.Price*80)/100) AS DECIMAL(6, 2)) FROM #Persons AS p
INNER JOIN cte_persons AS c ON c.ParentId = p.Id
)
SELECT * FROM cte_persons
This should be a two steps algorithm. First traverse the hierachy to get max level. Then apply the level in a reverse order.
WITH cte_persons
AS
(
SELECT 1 as level, p.Id, p.ParentId, p.Name, s.Price, p.Id AS base
FROM Persons AS p
INNER JOIN Sales AS s ON s.PersonId = p.Id
UNION ALL
SELECT level + 1, p.Id, p.ParentId, p.Name, c.Price, c.base
FROM Persons AS p
INNER JOIN cte_persons AS c ON c.ParentId = p.Id
)
SELECT Id, ParentId, Name,
CASE level WHEN 1
THEN price - sum(delta) over(partition by base order by level desc) + delta
ELSE delta END sale
FROM (
SELECT *,
(power (0.8000, max(level) over(partition by base) - level) * 0.2) * price delta
FROM cte_persons
) t
ORDER BY id;
db<>fiddle

Getting the primary (main), secondary and tertiary function for an emolyee

I have the following query to get the primary (main) and secondary function for each employee :
with
employeeScopeFunctions as (
select e.employeeId,
es.FunctionId,
ef.Label,
c.CompanyName,
realOrder = row_number() over(
partition by e.employeeId
order by isnull(es.sortOrder, 9999)
)
from employee e
LEFT JOIN employee_scope es on es.employeeId = e.employeeId
LEFT JOIN employee_function ef on es.FunctionId = ef.FunctionId
LEFT JOIN Company c ON es.CompanyId = c.ID
WHERE e.EmployeeId=54
)
select *,
primacy = iif(realOrder = 1, 'main', 'secondary')
from employeeScopeFunctions
For the EmployeeId=54 the result is like below :
EmployeeId FunctionId Label CompanyName realOrder Primacy
54 273 Group Chief Executive Officer C1 1 primary
54 273 Group Chief Executive Officer C2 2 secondary
54 273 Group Chief Executive Officer X5 3 secondary
54 897 Group Regional Chief Executive Officer X6 4 secondary
54 897 Group Regional Chief Executive Officer F6 5 secondary
54 39 Director VY 6 secondary
54 39 Director G7 7 secondary
What I want to get is regroup all the companies for a specific function and get three levels of primacy :
EmployeeId FunctionId Label CompanyName Primacy
54 273 Group Chief Executive Officer C1,C2,X5 primary
54 897 Group Regional Chief Executive Officer X6,F6 secondary
54 39 Director VY,G7 tertiary
If I followed you correctly, you could keep your existing CTE and turn on aggregation in the main query. ROW_NUMBER() can be used to rank the records by increasing realOrder.
This should work in SQL-Server:
WITH employeeScopeFunctions as (
SELECT
e.employeeId,
es.FunctionId,
ef.Label,
c.CompanyName,
realOrder = row_number() over(partition by e.employeeId order by isnull(es.sortOrder, 9999))
FROM
employee e
LEFT JOIN employee_scope es ON es.employeeId = e.employeeId
LEFT JOIN employee_function ef ON es.FunctionId = ef.FunctionId
LEFT JOIN company c ON es.CompanyId = c.ID
WHERE e.EmployeeId=54
)
SELECT
employeeId,
FunctionId,
Label,
CompanyName = STRING_AGG(CompanyName, ',') WITHIN GROUP (ORDER BY realOrder),
Primacy = CASE ROW_NUMBER() OVER(ORDER BY MIN(realOrder))
WHEN 1 THEN 'primary'
WHEN 2 THEN 'secondary'
WHEN 3 THEN 'tertiary'
END
FROM employeeScopeFunctions
GROUP BY
employeeId,
FunctionId,
Label
Alternative solution with an additional level of nesting to avoid nesting window function and aggregation:
WITH employeeScopeFunctions as (
SELECT
e.employeeId,
es.FunctionId,
ef.Label,
c.CompanyName,
realOrder = row_number() over(partition by e.employeeId order by isnull(es.sortOrder, 9999))
FROM
employee e
LEFT JOIN employee_scope es ON es.employeeId = e.employeeId
LEFT JOIN employee_function ef ON es.FunctionId = ef.FunctionId
LEFT JOIN company c ON es.CompanyId = c.ID
WHERE e.EmployeeId=54
)
SELECT
employeeId,
FunctionId,
Label,
CompanyName,
Primacy = CASE ROW_NUMBER() OVER(ORDER BY minRealOrder)
WHEN 1 THEN 'primary'
WHEN 2 THEN 'secondary'
WHEN 3 THEN 'tertiary'
END
FROM (
SELECT
employeeId,
FunctionId,
Label,
CompanyName = STRING_AGG(CompanyName, ',') WITHIN GROUP (ORDER BY realOrder),
minRealOrder = MIN(realOrder)
FROM employeeScopeFunctions
GROUP BY
employeeId,
FunctionId,
Label
) x

Inner join with one row of another table

**Table Employee**
Id Name
1 EmpName1
2 EmpName2
3 EmpName3
**Table EmpDeptHistory**
Id EmpId Dept Date
1 1 Housing 2015-03-02
2 2 Finance 2015-01-03
3 1 WareHouse 2015-05-02
4 2 Housing 2015-02-06
5 3 WareHouse 2015-02-02
6 1 Housing 2015-05-01
7 2 Finance 2015-01-02
8 2 Housing 2015-05-04
9 2 Finance 2015-05-02
10 1 WareHouse 2015-03-08
11 1 Housing 2015-02-20
I need find the recent dept with which every employee worked. Also I need to find for individual employee by passing EmpId
The following query returns only one employee and not all :(
SELECT e.id, edh.dept,edh.date
FROM Employee e
inner join (select top 1 eh.empid, eh.dept, eh.date
from EmpDeptHistory eh
order by eh.date desc) as edh
on e.id=edh.empid
yes, I understand the top 1 will give the emp id based on date, hence only one employee details is show. I am not sure how to get all the employee recent department.
select e.id,edh.dept,edh.date
from employee e
inner join EmpDeptHistory edh
on e.id = (Select eh.empid, eh.dept, eh.date
from EmpDeptHistory eh
where e.id=eh.empid
order by eh.date desc)
The above throws
The ORDER BY clause is invalid in views, inline functions, derived
tables, subqueries, and common table expressions, unless TOP,
OFFSET or FOR XML is also specified.
You can use CROSS APPLY to run the right-hand subquery once for each left-hand row:
SELECT e.id, edh.dept,edh.date
FROM Employee e cross apply ( select top 1 eh.empid, eh.dept, eh.date from
EmpDeptHistory eh where eh.empid = e.id order by eh.date desc) as edh
You can use a CTE and a ranking function like ROW_NUMBER:
WITH CTE AS
(
SELECT e.id, edh.dept, edh.date,
rn = ROW_NUMBER() OVER (PARTITION BY edh.EmpId ORDER BY edh.date DESC)
FROM Employee e inner join EmpDeptHistory edh
on e.id = edh.empid
)
SELECT id, dept, date
FROM CTE
WHERE rn = 1
DEMO
For the latest department for each employee, you can do it like so:
SELECT t1.*
FROM EmpDeptHistory t1 INNER JOIN
(
SELECT EmpId, MAX(Date) [Date]
FROM EmpDeptHistory
GROUP BY EmpId
) AS t2
ON t1.EmpId = t2.EmpId AND t1.Date = t2.Date
EmpId can be put into a where clause if needed.

Join one to many get one row, by priority

I have a contacts table:
ID NAME
--- ----
1 KK
2 JKI
3 HU
And I have a phone table:
ID ContactID Phone Type
--- --------- ----- --------
1 1 569 Business
2 1 896 Mobile
3 1 258 Fax
4 2 369 Mobile
5 3 124 Fax
6 2 496 Fax
I want to get all contacts with at least one phone number. The phone number to be displayed should be Business, if there are no Busniess Type available, then Mobile, if there are no Mobile type available then Fax else null
Sample Result:
ID NAME PHONE
--- ------ ------
1 KK 569 -- Business present
2 JKI 369 -- Business not present but mobile present
3 HU 124 -- only fax present
;WITH [prior](i,t) AS
(
SELECT 1, 'Business'
UNION ALL SELECT 2, 'Mobile'
UNION ALL SELECT 3, 'Fax'
),
x AS
(
SELECT c.ID, c.Name, p.Phone,
rn = ROW_NUMBER() OVER (PARTITION BY c.ID ORDER BY r.i)
FROM dbo.Contacts AS c
LEFT OUTER JOIN dbo.Phone AS p
ON p.ContactID = c.ID
LEFT OUTER JOIN [prior] AS r
ON r.t = p.[Type]
)
SELECT ID, Name, Phone FROM x
WHERE rn = 1;
If you want to eliminate contacts with no phones, just change both instances of LEFT OUTER to INNER.
select c.ID
, c.Name
, coalesce(business.Phone, mobile.Phone, fax.Phone) as Phone
from Contacts c
left join
Phone business
on business.ContactID = c.ID
and business.type = 'Business'
left join
Phone mobile
on mobile.ContactID = c.ID
and mobile.type = 'Mobile'
left join
Phone fax
on fax.ContactID = c.ID
and fax.type = 'Fax'
where coalesce(business.Phone, mobile.Phone, fax.Phone) is not null
Your data model is not great for querying this efficiently, but this may do the trick:
SELECT C.ID, C.Name, COALESCE(
(SELECT TOP 1 P.Phone FROM Phones P WHERE P.ContactID = C.ID AND P.[Type] = 'Business'),
(SELECT TOP 1 P.Phone FROM Phones P WHERE P.ContactID = C.ID AND P.[Type] = 'Mobile'),
(SELECT TOP 1 P.Phone FROM Phones P WHERE P.ContactID = C.ID AND P.[Type] = 'Fax')
) Phone
FROM Contacts C
The most compact solution I can think of is,
; WITH CTE AS (
SELECT
c.ID, c.NAME, p.Phone
, r = ROW_NUMBER()OVER(PARTITION BY c.ID ORDER BY CASE p.[TYPE] WHEN 'Business' THEN 1 WHEN 'Mobile' THEN 2 ELSE 3 END)
FROM Contacts c
LEFT JOIN Phone p ON p.ContactID = c.ID AND p.[TYPE] IN ('Business','Mobile','Fax')
WHERE EXISTS(SELECT 1 FROM Phone WHERE ContactID = c.ID)
)
SELECT ID, NAME, Phone
FROM CTE
WHERE r = 1;
This solution returns,
Contacts with the first matching phone in the specified order
Contact with NULL phone # if a phone # exists but none of the specified types
No result for Contacts having no phone at all