How to access columns from correlated subquery - sql

With below data i am trying to get employees of same location and department with type as manager whose salary is equals to sum of other employees sal along with emp ids as empids are unequal query not returning any result also grouping query should have more than 1 record
so result should not include Raghu record and also i want emp ids of all the records that are matched :
EMP_ID EMP_Name EMP_Loc EMP_Dept EMP_Sal Emp_type
1 Arjun Hyd Comp 1000 Manager
2 Ramesh Hyd Comp 500 Interim
3 Ragav Hyd Comp 300 Interim
4 Rajesh Hyd Comp 200 Interim
5 Raghu Hyd Comp 1000 Interim
select a.emp_dept , a.emp_loc ,a.emp_dept,b.emp_dept,a.emp_id,b.emp_id
from
(select sum(emp_sal) as sett,emp_loc,emp_dept,emp_id
from employee
where emp_type = 'Interim'
group by emp_loc,emp_dept,emp_id having count(emp_sal)>1
) a
inner join
(select emp_sal ,emp_loc,emp_dept,emp_id
from employee
where emp_type = 'Manager'
) b
on a.sett=b.emp_sal and a.emp_loc=b.emp_loc and a.emp_dept=b.emp_dept;

Sample data
solution:
SELECT a.EMP_ID, a.EMP_Name, a.EMP_Loc, a.EMP_Dept, a.EMP_Sal, a.Emp_type
FROM employee AS a INNER JOIN
(SELECT SUM(EMP_Sal) AS sal, EMP_Loc,EMP_Dept
FROM employee AS employee_1
WHERE (Emp_type = 'Interim')
GROUP BY EMP_Loc, EMP_Dept having Count(EMP_Sal)>1) AS b
ON a.EMP_Dept = b.EMP_Dept AND a.EMP_Loc = b.EMP_Loc AND a.EMP_Sal = b.sal
WHERE a.Emp_type= 'Manager'
Results:

Your problem is that you are including the emp_id in your first sub-select and grouping on it. This means that you get three rows, and the sum becomes pointless. There is never a case where the manager has 500, 200 or 300 salary, so the join doesn't work and there are therefore no records returned. If you do this instead it should work:
select a.emp_dept, a.emp_loc, b.emp_id
from
(select sum(emp_sal) as sett,emp_loc,emp_dept
from employee
where emp_type = 'Interim' and emp_sal > 1
group by emp_loc,emp_dept
) a
inner join
(select emp_sal ,emp_loc,emp_dept,emp_id
from employee
where emp_type = 'Manager'
) b
on a.sett=b.emp_sal and a.emp_loc=b.emp_loc and a.emp_dept=b.emp_dept;
Note that I also removed duplicate columns in the resultset. There is no point showing b.emp_dept as well as a.emp_dept, because the join condition guarantees that they will be the same.
EDIT
Following your revision to the question, I understand now what you are trying to do. This is really difficult to achieve, since what you need to do, is to take a cartesian sum of all the possible Interim salaries for a location and department to check if any possible combination of salary sums matches that of the Manager.
One possible way to do this would be the following:
WITH cte as
(SELECT a.emp_id as aempid, b.emp_id as bempid, 0 as cempid, 0 as dempid, a.emp_sal + b.emp_sal AS SumSal, a.emp_loc, a.emp_dept
FROM employee a
CROSS JOIN employee b WHERE a.emp_loc = b.emp_loc AND a.emp_dept = b.emp_dept
AND a.emp_type = b.emp_type AND a.emp_type = 'Interim' AND a.emp_id <> b.emp_id
AND b.emp_id > a.emp_id
UNION
SELECT a.emp_id, b.emp_id, c.emp_id, 0, a.emp_sal + b.emp_sal + c.emp_sal AS SumSal, a.emp_loc, a.emp_dept
FROM employee a
CROSS JOIN employee b
CROSS JOIN employee c
WHERE a.emp_loc = b.emp_loc AND a.emp_dept = b.emp_dept
AND a.emp_type = b.emp_type AND a.emp_type = 'Interim' AND a.emp_id <> b.emp_id
AND a.emp_loc = c.emp_loc AND a.emp_dept = c.emp_dept
AND a.emp_type = c.emp_type AND a.emp_id <> c.emp_id AND b.emp_id <> c.emp_id
AND b.emp_id > a.emp_id AND c.emp_id > b.emp_id
UNION
SELECT a.emp_id, b.emp_id, c.emp_id, d.emp_id, a.emp_sal + b.emp_sal + c.emp_sal + d.emp_sal AS SumSal, a.emp_loc, a.emp_dept
FROM employee a
CROSS JOIN employee b
CROSS JOIN employee c
CROSS JOIN employee d
WHERE a.emp_loc = b.emp_loc AND a.emp_dept = b.emp_dept
AND a.emp_type = b.emp_type AND a.emp_type = 'Interim' AND a.emp_id <> b.emp_id
AND a.emp_loc = c.emp_loc AND a.emp_dept = c.emp_dept
AND a.emp_type = c.emp_type AND a.emp_id <> c.emp_id AND b.emp_id <> c.emp_id
AND a.emp_loc = d.emp_loc AND a.emp_dept = d.emp_dept
AND a.emp_type = d.emp_type AND a.emp_id <> d.emp_id AND b.emp_id <> d.emp_id AND c.emp_id <> d.emp_id
AND b.emp_id > a.emp_id AND c.emp_id > b.emp_id AND d.emp_id > c.emp_id
)
SELECT m.emp_id as managerid, cte.aempid, cte.bempid, cte.cempid, cte.dempid, m.emp_sal, m.emp_loc, m.emp_dept
FROM employee m
INNER JOIN cte ON cte.emp_loc = m.emp_loc AND cte.emp_dept = m.emp_dept AND m.emp_sal = cte.sumsal
WHERE m.emp_type = 'Manager';
This is really ugly, but I could think of nothing better, and at least it returns the desired result. In practice of course, you would have to create it dynamically to match the maximum number of Interim employees in any one location/department.
A couple of points in case it is not clear. With this approach there is no need for a HAVING COUNT(*) > 1 since the requirement a.emp_id <> b.emp_id etc. guarantees that there are at least two different Interim employees. Also the requirement that b.emp_id > a.emp_id (and c and d) is there to ensure that each possible combination of a, b, c and d only occurs once (so in this case we get just 2, 3, 4 (in that order) plus 0 for d, not all possible combinations of 2, 3, and 4.

You are looking for all groups of employees that have a total salary that equals their manager's salary. In order to find all these groups you need a recursive query in SQL. In PostgreSQL you can use an array to collect the employees in that query.
Sample data:
emp_id
emp_type
salary
1
Manager
1000
2
Interim
500
3
Interim
500
4
Interim
300
5
Interim
200
Result:
manager_id
employee_ids
1
{2,3}
1
{2,4,5}
1
{3,4,5}
The query:
with recursive cte(manager_id, manager_salary, employee_ids, total, loc, dept) as
(
select emp_id, emp_sal, array[]::integer[], 0, emp_loc, emp_dept
from employee
where emp_type = 'Manager'
union
select cte.manager_id, cte.manager_salary, cte.employee_ids || e.emp_id,
cte.total + e.emp_sal, cte.loc, cte.dept
from cte
join employee e
on e.emp_loc = cte.loc
and e.emp_dept = cte.dept
and e.emp_type <> 'Manager'
and e.emp_id > all(cte.employee_ids)
and cte.total + e.emp_sal <= cte.manager_salary
)
select manager_id, manager_salary, employee_ids
from cte
where total = manager_salary
and array_length(employee_ids, 1) > 1
order by manager_id, employee_ids;
Demo: https://dbfiddle.uk/?rdbms=postgres_13&fiddle=5311cdaebdc875440d9e226ace8dfefc
As a bonus:
At last the query with the employee IDs unnested, so you see the groups in multiple line with one line per employee:
with recursive cte(manager_id, manager_salary, employee_ids, total, loc, dept) as
(
select emp_id, emp_sal, array[]::integer[], 0, emp_loc, emp_dept
from employee
where emp_type = 'Manager'
union
select cte.manager_id, cte.manager_salary, cte.employee_ids || e.emp_id,
cte.total + e.emp_sal, cte.loc, cte.dept
from cte
join employee e
on e.emp_loc = cte.loc
and e.emp_dept = cte.dept
and e.emp_type <> 'Manager'
and e.emp_id > all(cte.employee_ids)
and cte.total + e.emp_sal <= cte.manager_salary
)
select cte.manager_id, cte.manager_salary,
dense_rank() over (partition by cte.manager_id order by cte.employee_ids) as grp,
e.*
from cte
cross join lateral unnest(employee_ids) link(employee_id)
join employee e on e.emp_id = link.employee_id
where cte.total = cte.manager_salary
and array_length(cte.employee_ids, 1) > 1
order by cte.manager_id, grp, e.emp_id;
Demo: https://dbfiddle.uk/?rdbms=postgres_13&fiddle=7b6218f28cbf1e9d4c1a0c76c46b9601

Related

Oracle SQL MAX and GROUP BY Costs too Much Lines of Code

I have this long query, where I want to fetch some data about employees:
SELECT e.id,
e.emp_number,
TO_CHAR(SYSDATE,'DD/MONTH/YYYY') "GREGORIAN",
to_char(sysdate,'DD-MM-YYYYY','nls_calendar=''arabic hijrah''') HIJRI,
ba.acc_number "Account Number"
to_char(c.id) "National ID",
en.name
FROM
relationships r,
rel_actions ra,
actions a,
payrolls p,
emp_names en,
citizenships c,
pay_methods pm,
bank_accounts ba,
assignments as,
emp e
WHERE r.id = ra.id
AND r.id=pm.id
AND as.id = e.id
AND r.id = e.id
AND en.id = e.id
AND en.NAME_TYPE ='GLOBAL'
AND a.action_type = 'T'
AND a.id = ra.id
AND a.id = p.id
and c.id = e.id
and ba.id=pm.id
AND a.effective_date BETWEEN ba.start_date AND ba.end_date
AND a.effective_date BETWEEN p.effective_start_date AND p.effective_end_date
AND a.effective_date BETWEEN r.start_date AND r.end_date
AND a.effective_date BETWEEN en.effective_start_date AND en.effective_end_date
AND a.effective_date BETWEEN e.effective_start_date AND e.effective_end_date
AND a.effective_date BETWEEN pm.effective_start_date AND pm.effective_end_date
AND a.effective_date BETWEEN as.effective_start_date AND as.effective_end_date
AND as.assignment_type = 'E'
AND SYSDATE BETWEEN as.effective_start_date AND as.effective_end_date
ORDER BY e.emp_number
the result of this query will be something like this :
emp_number account_number name national_id gregorian hijri
1 6456 john ^*&$^**$^** 6/12/2022 12/5/1444
1 6456 john ^*&$^**$^** 6/12/2022 12/5/1444
2 4121 Mathew %&#%^%&%&%^ 6/12/2022 12/5/1444
2 4121 Mathew %&#%^%&%&%^ 6/12/2022 12/5/1444
taking the first 2 rows for example, they have different effective_date, so I want to fetch the row that have the newest effective_date:
and a.effective_date in (
select effective_Date from pay_payroll_actions
where a.effective_date BETWEEN ba.start_date AND ba.end_date
AND a.effective_date BETWEEN p.effective_start_date AND p.effective_end_date
AND a.effective_date BETWEEN r.start_date AND r.end_date
AND a.effective_date BETWEEN en.effective_start_date AND en.effective_end_date
AND a.effective_date BETWEEN e.effective_start_date AND e.effective_end_date
AND a.effective_date BETWEEN pm.effective_start_date AND pm.effective_end_date
AND a.action_type = 'T'
AND a.id = ra.id
AND a.id = p.id
)
GROUP BY e.id, e.emp_number,
TO_CHAR(SYSDATE,'DD/MONTH/YYYY'),
to_char(sysdate,'DD-MM-YYYYY','nls_calendar=''arabic hijrah'''),
ba.acc_number ,
to_char(c.id),
en.name
my question is, do I really need to apply all related conditions in the sub-query in order to get the same effective dates resulted from the main query?
and if yes, its too long, is there a way to shorten this? thanks in advance
You can filter the duplicate rows using row_number ()
WITH cte
AS
(SELECT e.id,
e.emp_number,
TO_CHAR(SYSDATE,'DD/MONTH/YYYY') "GREGORIAN",
to_char(sysdate,'DD-MM-YYYYY','nls_calendar=''arabic hijrah''') HIJRI,
ba.acc_number "Account Number"
to_char(c.id) "National ID",
en.name,
row_number() over (partition BY e.id,
e.emp_number,ba.acc_number,c.id,en.name ORDER BY a.effective_date desc) AS dup_count
FROM your_query)
SELECT * FROM cte WHERE dup_count = 1;
OR Since you are already checking the dates between condition in your main query ,you can refine your sub query to
and a.effective_date in (
select effective_Date from pay_payroll_actions py
where a.id=py.id
)

An item with the same key has already been added in SSRS

Why I am getting the error:
An item with the same key has already been added.
In ssrs for code:
SELECT e.id AS eid,
p.dscr AS jdscr,
e.startdate AS hdate,
Concat(e.fname,e.lname) AS NAME,
m.weekenddate AS weekenddate
m.bramt AS bramt,
m.conamt AS conamt,
w.twork AS twork
FROM tblemp e
LEFT JOIN
(
SELECT cid,
weekenddate,
COALESCE((
CASE
WHEN mrtype = 'BR' THEN sum(mgamt)
END),0) AS bramt ,
COALESCE((
CASE
WHEN mrtype = 'con' THEN sum(mgamt)
END),0) AS conamt
FROM mngrtbl
GROUP BY cid,
weekendate,
mrtype) m ON e.id = m.cid
LEFT JOIN
(
SELECT emp,
sum(twork) AS twork
FROM tblans
GROUP BY emp) w ON e.id = w.emp
LEFT JOIN pname p ON e.pid = p.ponid

count after join on multiple tables and count of multiple column values

Please help me with below problem.
table 1 employee details
emp name empno.
---------------------------------
John 1234
Joe 6789
table 2 employee assignment
empno assignmentstartdate assignmentenddate assignmentID empassignmentID
-----------------------------------------------------------------------------
1234 01JAN2017 02JAN2017 A1 X1
6789 01jan2017 02JAN2017 B1 Z1
table 3 employee assignment property
empassignmentID assignmentID propertyname propertyvalue
-------------------------------------------------------------------
X1 A1 COMPLETED true
X1 A1 STARTED true
Z1 B1 STARTED true
Z1 B1 COMPLETED false
Result wanted: (count of completed and started for each employee)
emp name emp no. COMPLETED STARTED
------------------------------------------
John 1234 1 1
Joe 6789 0 1
Currently with my query it is not putting count correctly for propertyvalue if I run for one employee it works correctly but not for multiple employees.
Please help.
SELECT empno ,
empname ,
(SELECT COUNT(A.propertyvalue)
FROM employeedetails C ,
employees_ASSIGNMENT RCA,
employee_assignment_property A
WHERE TRUNC(startdate) >= '14jun2017'
AND TRUNC(endate) <= '20jun2017'
AND RCA.empno = C.empno
AND RCA.empassignmetid = A.empassignmetid
AND rca.EMPNO IN ('1234','6789')
AND RCA.assignmentid = A.assignmentid
AND A.Name = 'COMPLETED'
AND A.propertyvalue = 'true') ,
(SELECT COUNT(A.propertyvalue)
FROM employeedetails C ,
employees_ASSIGNMENT RCA,
employee_assignment_property A
WHERE TRUNC(startdate) >= '14jun2017'
AND TRUNC(endate) <= '20jun2017'
AND RCA.empno = C.empno
AND RCA.empassignmetid = A.empassignmetid
AND rca.EMPNO IN ('1234','6789')
AND RCA.assignmentid = A.assignmentid
AND A.Name = 'STARTED'
AND A.propertyvalue = 'true')FROM employeedetails WHERE EMPNO IN
('1234','6789') GROUP BY C.empno ,
C.EMPNAME
I think you are simply looking for this:
SELECT DET.empname
, COUNT(CASE WHEN PROP.propertyname = 'COMPLETED' THEN 1 END) COMP_COUNT
, COUNT(CASE WHEN PROP.propertyname = 'STARTED' THEN 1 END) START_COUNT
FROM employeedetails DET
INNER JOIN employees_ASSIGNMENT ASS
ON ASS.empno = DET.empno
INNER JOIN employee_assignment_property PROP
ON PROP.empassignmentID = ASS.empassignmentID
AND PROP.assignmentID = ASS.assignmentID
GROUP BY DET.empname
Just add a WHERE clause if you need one.
if you want you result as a query without CTEs this should work:
select empName,
empNo,
(select employee_details.empNo, count(employee_assignment.assId)
from employee_details as t1
join employee_assignment on (t1.empno = employee_assignment.empno)
join employee_assignment_property on (employee_assignment.assId = employee_assignment_property.assId)
where employee_assignment.ptop = 'COMPLETED'
and t.empNo = t1.empNo
group by t1.empNo ) as [COMPLETED],
(select employee_details.empNo, count(employee_assignment.assId)
from employee_details as t1
join employee_assignment on (t1.empno = employee_assignment.empno)
join employee_assignment_property on (employee_assignment.assId = employee_assignment_property.assId)
where employee_assignment.ptop = 'STARTED'
and t.empNo = t1.empNo
group by t1.empNo ) as [STARTED],
from employee_details as t
If you don't want to do a dirty query composed of subqueries, you can try creating a view (if your database permits it).
What does it mean : I'll be useless in front of this. In summary, a view is a temporary table.
Hope this helps
this should work using CTEs:
Using Common Table Expressions
with numComplet()
as
(
select tbl1.empNo, count(tbl2.assId)
from tbl1
join tbl2 on (tbl1.empno = tbl2.empno)
join tbl3 on (tbl2.assId = tbl3.assId)
where tbl2.ptop = 'COMPLETED'
group by tbl1.empNo
),
with numStarted()
as
(
select tbl1.empNo, count(tbl2.assId)
from tbl1
join tbl2 on (tbl1.empno = tbl2.empno)
join tbl3 on (tbl2.assId = tbl3.assId)
where tbl2.ptop = 'STARTED'
group by tbl1.empNo
)
select *
from tbl1
join numComplet on (tbl1.empNo = numComplet.empNo)
join numStarted on (tbl1.empNo = numStarted.empNo)
I put down table names as tbl[1|2|3]

Recursion in sql with loop

I have a table,
where there are users and each of them has supervisior. However the CEO does not have a supervisior and he is supervisior of himself. (i.e, supervisiorid of CEO is UserId of CEO)
I have a requirement to find list of all subordinates under a given userid. I am using below query.
WITH CTE_EMPLOYEE_HEIRARCHY AS
(
SELECT E.UserId, E.SupervisiorId AS Supervisor, d.DepartmentName,d.DepartmentId
FROM T_BIT_Users E with (nolock) inner join T_BIT_Department d with (nolock) on d.DepartmentId=e.DepartmentId WHERE E.UserId = 13
UNION ALL
SELECT E1.UserId, E1.SupervisiorId AS Supervisor,d.DepartmentName,d.DepartmentId
FROM CTE_EMPLOYEE_HEIRARCHY
JOIN T_BIT_Users E1
ON E1.SupervisiorId = CTE_EMPLOYEE_HEIRARCHY.UserId
inner join T_BIT_Department d on d.DepartmentId=e1.DepartmentId)
SELECT * FROM CTE_EMPLOYEE_HEIRARCHY
OPTION ( MAXRECURSION 0 )
this keeps on going in loop forever.
Any suggestions.?
Just limit the recursive part to exclude where supervisorId = userId
SELECT E1.UserId, E1.SupervisiorId AS Supervisor,d.DepartmentName,d.DepartmentId
FROM CTE_EMPLOYEE_HEIRARCHY
JOIN T_BIT_Users E1
ON E1.SupervisiorId = CTE_EMPLOYEE_HEIRARCHY.UserId
inner join T_BIT_Department d on d.DepartmentId=e1.DepartmentId
WHERE E1.SupervisorId != E1.UserId # <-- here

can we have CASE expression/case result as a join table name in oracle

I have 3 tables say Employee, Permanent_Emp and Contract_Emp
SELECT E.EMP_NO,
E.NAME,
JET.EMP_TYPE,
JET.DATE_JOINED
FROM Employee E
LEFT OUTER JOIN
/* Here Join Table Name(JET) it can be Permanent_Emp or Contract_Emp
which i want as a result of my case expression. */
ON (some condition here) ORDER BY E.EMP_NO DESC
case expression:
CASE
WHEN (E.EMP_TYPE_CODE >10 )
THEN
Permanent_Emp JET
ELSE
Contract_Emp JET
END
Note: table and column names are just for an example to understand requirement.
how can i have join table name from a case expression?
Something like this (although without a description of your tables, the exact join conditions or any sample data its hard to give a more precise answer):
SELECT E.EMP_NO,
E.NAME,
COALESCE( P.EMP_TYPE, C.EMP_TYPE ) AS EMP_TYPE
COALESCE( P.DATE_JOINED, C.DATE_JOINED ) AS DATE_JOINED
FROM Employee E
LEFT OUTER JOIN
Permanent_Emp P
ON ( E.EMP_TYPE_CODE > 10 AND E.EMP_NO = P.EMP_NO )
LEFT OUTER JOIN
Contract_Emp C
ON ( E.EMP_TYPE_CODE <= 10 AND E.EMP_NO = C.EMP_NO )
ORDER BY
E.EMP_NO DESC
use your case in select and join both tables
as
SELECT case when 1 then a.column
when 2 then b.column
end
from table c
join table a
on 1=1
join table2 b
on 1=1
but you cant use case while joining. its better to join both tables and in select use case statement with conditions as per your requirement
There is no way to conditionally add tables to a query in static SQL. If the relevant columns in Permanent_Emp and Contract_Emp are roughly equivalent, you could use a union in a sub-query.
SELECT *
FROM employee e
JOIN
(SELECT employee_id, relevant_column, 'P' AS source_indicator
FROM permanent_emp
UNION ALL
SELECT employee_id, relevant_column, 'C' AS source_indicator
FROM contract_emp) se
ON e.employee_id = se.employee_id
AND ( (e.emp_type_code > 10 AND source_indicator = 'P')
OR (e.emp_type_code <= 10 AND source_indicator = 'C'))
Using Alan's query as a starting point you can still use a case statement, just move it to the join condition:
SELECT *
FROM employee e
JOIN (
SELECT employee_id
, relevant_column
, 'P' AS source_indicator
FROM permanent_emp
UNION ALL
SELECT employee_id
, relevant_column
, 'C' AS source_indicator
FROM contract_emp
) se
ON se.employee_id = e.employee_id
and se.source_indicator = case when e.emp_type_code > 10
then 'P'
else 'C'
end
The only difference between this query and Allan's is the use of a case statement instead of an or statement.