Oracle: replace "rollup" in query with something else - sql

I need to rewrite a simple query without rollup function. Could you help me?
This is an original query:
SELECT e.department_id,
e.job_id,
SUM(e.salary)
FROM EMPLOYEES e
GROUP BY ROLLUP(e.department_id, e.job_id);
I guess it is possible to rewrite using UNION statement, yea?

No need for a UNION, you can use GROUPING SETS. This will produce the same results and even the same explain plan:
SELECT e.department_id,
e.job_id,
SUM(e.salary)
FROM EMPLOYEES e
GROUP BY GROUPING SETS( (e.department_id, e.job_id), (e.department_id), () )

The following should return the same result as a rollup, but with worse performance and less controll over the "levels".
select e.department_id
,e.job_id
,SUM(e.salary)
from EMPLOYEES e
group
by e.department_id
,e.job_id
union all
select e.department_id
,null
,SUM(e.salary)
from EMPLOYEES e
group
by e.department_id
union all
select null
,null
,SUM(e.salary)
from EMPLOYEES e;

You can use a CTE to handle it (note I created the EMPLOYEE table via the connect by just to have sample data). There are probably better ways to do this, but this is a way!
WITH EMPLOYEES AS(
SELECT MOD(LEVEL,5) DEPARTMENT_ID
, LEVEL JOB_ID
, 1000*LEVEL SALARY
FROM DUAL
CONNECT BY LEVEL < 10
)
, SUMMEDDATA AS(
SELECT e.department_id,
e.job_id,
SUM(e.salary) SUMMED_SALARY
FROM EMPLOYEES e
GROUP BY e.department_id, e.job_id
)
, SUMMEDJOB_ID AS(
SELECT e.department_id,
SUM(e.salary) SUMMED_SALARY
FROM EMPLOYEES e
GROUP BY e.department_id
)
, SUMMEDTOTAL AS(
SELECT
SUM(e.salary) SUMMED_SALARY
FROM EMPLOYEES e
)
SELECT DEPARTMENT_ID ,
JOB_ID ,
SUMMED_SALARY
FROM SUMMEDDATA
UNION ALL
SELECT DEPARTMENT_ID ,
NULL ,
SUMMED_SALARY
FROM SUMMEDJOB_ID
UNION ALL
SELECT NULL ,
NULL ,
SUMMED_SALARY
FROM SUMMEDTOTAL
ORDER BY 1 NULLS LAST, 2 NULLS LAST ;
DEPARTMENT_ID JOB_ID SUMMED_SALARY
---------------------- ---------------------- ----------------------
0 5 5000
0 5000
1 1 1000
1 6 6000
1 7000
2 2 2000
2 7 7000
2 9000
3 3 3000
3 8 8000
3 11000
4 4 4000
4 9 9000
4 13000
45000

Related

How to query multiple Child data against one Master data?

I want to SELECT one parent and all the child under it. What Oracle does is it keeps showing the parents against its child.
Please tell me how to show the following query in ORACLE from this:
select d.department_name, e.last_name from employees e join departments d
on (d.department_id=e.department_id)
group by d.department_name, e.last_name
order by 1;
DEPARTMENT_NAME LAST_NAME
------------------------------ ------------
Accounting Gietz
Accounting Higgins
Executive De Haan
Executive King
Executive Kochhar
-------------------------------------------
Like this:
DEPARTMENT_NAME LAST_NAME
------------------------------ ------------
Accounting Gietz
Higgins
Executive De Haan
King
Kochhar
-------------------------------------------
You can use analytical function as following:
Select case when rn = 1 then department_name end as dept
,last_name from
(select d.department_name, e.last_name,
Row_number() over (partition by d.department_name order by e.last_name) as rn
from employees e join departments d
on (d.department_id=e.department_id)
group by d.department_name, e.last_name)
order by department_name, rn;
Cheers!!
This type of data transformation is best done at the application layer. But if you want to do it in SQL, you can use window functions -- but with the caveat that the ordering of the rows is very important. I would suggest:
select (case when row_number() over (partition by d.department_id order by e.last_name) = 1
then d.department_name
end) as dept,
last_name
from employees e join
departments d
on d.department_id = e.department_id
group by d.department_name, e.last_name
order by d.department_name, dept nulls last;
I also sincerely doubt that you need the group by, unless the tables have duplicates. So:
select (case when row_number() over (partition by d.department_id order by e.last_name) = 1
then d.department_name
end) as dept,
last_name
from employees e join
departments d
on d.department_id = e.department_id
order by d.department_name, dept nulls last;

how select max(salary) of employee each department with employee_id and emp_name

I want to select emp_id,department_id,max(salary) each department but I use group by department_id and it has error ora-00979
3 column is in the same table(employees)
How can I fix it
select department_id, employee_id as "ID",first_name || ' ' || last_name as "Name",max(salary)as "SALARY"
from EMPLOYEES
group by department_id
order by department_id;
Use a subquery with department_id and the max salary and then join to the main table:
select
e.department_id,
t.employee_id as id,
t.first_name || ' ' || t.last_name as name,
e.maxsalary
from (
select
department_id,
max(salary) as maxsalary
from
EMPLOYEES
group by
department_id
) e
inner join
EMPLOYEES t
on
t.department_id = e.department_id and t.salary = e.maxsalary
order by e.department_id;
See the demo
EMPLOYEES
EMPLOYEE_ID DEPARTMENT_ID SALARY FIRST_NAME LAST_NAME
1 1 10000 A B
2 1 20000 C D
3 1 150000 E F
4 2 12000 G H
5 2 10000 I J
6 3 20000 K L
7 4 11000 M N
8 4 11000 O P
9 4 11000 Q R
10 4 10000 S T
Result
DEPARTMENT_ID ID NAME MAXSALARY
1 3 E F 150000
2 4 G H 12000
3 6 K L 20000
4 7 M N 11000
4 8 O P 11000
4 9 Q R 11000
You can use keep:
select department_id,
max(employee_id) keep (dense_rank first order by salary desc) as "ID",
max(first_name || ' ' || last_name) keep (dense_rank first order by salary desc, employee_id desc) as "Name",
max(salary) as "SALARY"
from employees e
group by department_id
order by department_id;
Try this one:
SELECT department_id, salary AS "Salary", employee_id AS "ID", first_name || ' ' || last_name AS "Name" FROM employees
WHERE salary = (SELECT MAX(salary) FROM employees) GROUP BY department_id;
I hope it works. :)
The error is because employee_id is not in the group.
A possible solution is:
select department_id, ID, Name, SALARY
from (
select distinct department_id,
first_value(employee_id) over (partition by department_id order by salary desc) as ID,
first_value(first_name || ' ' || last_name) over (partition by department_id order by salary desc) as Name,
first_value(salary) over (partition by department_id order by salary desc)as SALARY
from EMPLOYEES
)
order by department_id;

Combine top and join in sql

I have two tables. One for employees
LAST_NAME SALARY DEPARTMENT_ID
------------------------- ---------- -------------
Vargas 2500 50
Zlotkey 10500 50
Abel 11000 80
Taylor 8600 80
One for department name
DEPARTMENT_ID DEPARTMENT_NAME
------------- ------------------------------
50 Shipping
80 Sales
I want to select the top three employees who have the max salary in the employees table.After get, I want to get their department_name. Resulting like this.
LAST_NAME SALARY DEPARTMENT_NAME
------------------------- ---------- -------------
Abel 11000 Sales
Zlotkey 10500 Shipping
Taylor 8600 Sales
I had try this:
SELECT last_name, salary, department_id, ROWNUM as RANK
FROM (SELECT last_name, salary, department_id
FROM employees
ORDER BY salary DESC)
WHERE ROWNUM <= 3;
But i don't know how to use join on to get department_name.
Platform: windows10
SQLDeveloper version: 18.01
You can do this using window functions:
select e.last_name, e.salary, d.department_name
from (select e.*, max(sum_salary) over () as max_sum_salary
from (select e.*, sum(e.salary) over (partition by department_id) as sum_salary
from employees e
) e
) e join
department d
on e.department_id = d.department_id
where max_sum_salary = sum_salary
try this
select top(3)LAST_NAME, SALARY, DEPARTMENT_NAME
from employees e
inner join department d on e.DEPARTMENT_ID = d.DEPARTMENT_ID
where d.DEPARTMENT_NAME = 'sales'
order by SALARY desc
You can do JOIN with subquery :
SELECT e.LAST_NAME, e.SALARY, d.DEPARTMENT_NAME
FROM employees e INNER JOIN
department d
ON d.DEPARTMENT_ID = e.DEPARTMENT_ID
WHERE e.DEPARTMENT_ID = (SELECT e1.DEPARTMENT_ID
FROM employees e1
ORDER BY e1.SALARY DESC
FETCH FIRST 1 ROWS ONLY
);
EDIT : If you want only three employees then you can do :
SELECT e.LAST_NAME, e.SALARY, d.DEPARTMENT_NAME
FROM employees e INNER JOIN
department d
ON d.DEPARTMENT_ID = e.DEPARTMENT_ID
ORDER BY e.SALARY DESC
FETCH FIRST 3 ROWS ONLY

Count all subordinates who directly or indirectly reports to managers

I got a problem with one task.
I need to count all subordinates (distinct) who directly or indirectly reports to a specific manager
I have an Employee table like this:
EMPLOYEE_ID Int,
MANAGER_ID Int,
EMPLOYEE_NAME varchar(200)
Example:
Alex(1)
--------------------
Jhon(2) Kevin(3)
------------------------------
Mike(4) Amanda(5) Tom(6) Jery(7)
I can count only employee who directly reports to a manager:
SELECT
MANAGER_ID
,COUNT(MANAGER_ID) as SubCount
FROM [dbo].[EMPLOYEE]
GROUP BY MANAGER_ID
But in result I have something like this:
Manager_ID | SubCount
----------------------
1 | 2
2 | 2
3 | 2
----------------------
Instead:
Manager_ID | SubCount
----------------------
1 | 6
2 | 2
3 | 2
----------------------
I will be happy for any suggestions or idea how to do this.
declare #t table(EMPLOYEE_ID Int,
MANAGER_ID Int,
EMPLOYEE_NAME varchar(200))
insert #t values(1,null,'Alex'),(2,1,'Jhon'),(3,1,'Kevin'),
(4,2,'Mike'),(5,2,'Amanda'),(6,3,'Tom'),(7,3,'Jerry')
;with a as
(
select EMPLOYEE_ID boss,EMPLOYEE_ID from #t t
where exists (select 1 from #t where t.EMPLOYEE_ID = MANAGER_ID)
union all
select a.boss, t.EMPLOYEE_ID
from #t t join a on t.MANAGER_ID = a.EMPLOYEE_ID
)
--subtracting 1 because it is also counting the manager
select boss, count(*)-1 SubCount from a group by boss
option (maxrecursion 20)
You need to use a recursive CTE for this:
with managers as (
select employee_id, manager_id, 1 as level
from employees e
union all
select e.employee_id, m.manager_id, e.level+1
from managers m join
employees e
on m.manager_id = e.employee_id
)
select m.manager_id, count(*)
from managers m
group by m.manager_id;
The recursive CTE creates all employee/manager pairs. The final query does the aggregation and counting.
EDIT:
It sounds like there are cycles in the employee/manager relationship. I think the following fixes this (I don't have SQL Server available right now to test it):
with managers as (
select employee_id, manager_id, 1 as level
from employees e
union all
select e.employee_id, m.manager_id, e.level+1
from managers m join
employees e
on m.manager_id = e.employee_id
where e.employee_id not in (select employee_id from managers)
)
select m.manager_id, count(*)
from managers m
group by m.manager_id;

How do I get a single row value in a group statement?

I have two tables, JobTable and EmployeeTable with the following data:
EmployeeTable:
EmpId Salary
1 10
2 20
3 30
4 40
5 50
6 60
JobTable:
JobId EmpId
A 1
A 2
B 3
B 4
C 5
C 6
I need an SQL statement that will return the EmpId of the Employee with the minimum salary for each Job.
You could use the RANK() function like this:
WITH ranked AS (
SELECT
j.JobId,
e.EmpId,
e.Salary,
RANK() OVER (PARTITION BY j.JobId ORDER BY e.Salary) AS rnk
FROM JobTable j
INNER JOIN EmployeeTable e ON j.EmpId = e.EmpId
)
SELECT
JobId,
EmpId,
Salary,
FROM ranked
WHERE rnk = 1
Hmm, give this one a shot:
SELECT st.EmpID, min(st.salary)
FROM SalaryTable st INNER JOIN JobTable jt ON st.EmpID=jt.EmpID
WHERE jt.JobID = 'A'
GROUP BY st.EmpID