Group functions with Hierarchical Query - sql

I have the following table employees(emp_id, name,salary, manager_id)
I want to write query to retrieve manager_id and summation of all salaries of employees who are managed by this manager or even managed by manager who is managed by this manager.
I wrote query like this:
Select manager_id , sum(salary)
from employees
connect by prior emp_id = manager_id
start manager_id = 100
group by manager_id;
but that doesn't retrieve sum salary as I want.

Build hierachy first remembering ROOT, then group by root. E.g. salary of the manager which emp_id=100 and all employees he/she managers:
SELECT manager_id, SUM(salary) "Total_Salary"
FROM (
SELECT CONNECT_BY_ROOT emp_id as manager_id, Salary
FROM emploees
START WITH emp_id=100
CONNECT BY PRIOR emp_id = manager_id )
GROUP BY manager_id
ORDER BY manager_id;

#Serg's solution is fine, but for one manager even simpler query works:
select 21 as id, sum(salary) as summed
from employees e
start with emp_id = 21
connect by prior emp_id = manager_id;
If you don't want manager's salary in sum then add where level<>1.
Test data:
create table employees(emp_id number(4), name varchar2(10),
salary number(6), manager_id number(4));
insert into employees values ( 1, 'King', 10000, null);
insert into employees values ( 11, 'Smith', 8000, 1);
insert into employees values ( 21, 'Jones', 9000, 1);
insert into employees values ( 211, 'Brown', 7500, 21);
insert into employees values ( 212, 'Adams', 6200, 21);
insert into employees values (2111, 'White', 5000, 211);
Output:
ID SUMMED
------ ----------
21 27700

Related

Analytic functions and plain SQL equivalent

I'm using Oracle and SQL Developer. I have downloaded HR schema and need to do some queries with it. Now I'm working with table Employees. As an user I need to see the list of employees with lowest salary in each department. I need to provide different solutions by means of plain SQL and one of analytic functions. About analytic functions, I have used RANK():
SELECT *
FROM
(SELECT
employee_id,
first_name,
department_id,
salary,
RANK() OVER (PARTITION BY department_id
ORDER BY salary) result
FROM
employees)
WHERE
result = 1
AND department_id IS NOT NULL;
The result seems correct:
but when I try to use plain SQL I actually get all employees with their salaries.
Here is my attempt with GROUP BY:
SELECT
department_id, MIN(salary) AS "Lowest salary"
FROM
employees
GROUP BY
department_id;
This code seems good, but I need to also get columns first_name and employee_id.
I tried to do something like this:
SELECT
employee_id,
first_name,
department_id,
MIN(salary) result
FROM
employees
GROUP BY
employee_id,
first_name,
department_id;
and this:
SELECT
employee_id,
first_name,
salary,
departments.department_id
FROM
employees
LEFT OUTER JOIN
departments ON (employees.department_id = departments.department_id)
WHERE
employees.salary = (SELECT MIN(salary)
FROM departments
WHERE department_id = employees.department_id)
These seem wrong. How can I change or modify my queries to get the same result as when I'm using RANK() by means of plain SQL (two solutions at least)?
One of the options could be like here (with old EMP table)...
SELECT EMPNO, ENAME, DEPTNO, SAL
FROM EMP e
WHERE SAL = (Select MIN_SAL From (SELECT DEPTNO, Min(SAL) "MIN_SAL"
FROM EMP
GROUP BY DEPTNO)
Where DEPTNO = e.DEPTNO)
ORDER BY DEPTNO, SAL;
Second option could be...
SELECT EMPNO, ENAME, DEPTNO, SAL
FROM (SELECT e.EMPNO, e.ENAME, e.DEPTNO, e. SAL, (Select Min(SAL) "MIN_SAL" From EMP Where DEPTNO = e.DEPTNO) "MIN_SAL" From EMP e)
WHERE SAL = MIN_SAL
ORDER BY DEPTNO, SAL;
Regards...
You can use a subquery to find the lowest salary per employee and use the main query to only show the information of those employees that are selected by this subquery:
SELECT
employee_id,
first_name,
department_id,
salary
FROM employees e1
WHERE salary =
(SELECT MIN(e2.salary)
FROM employees e2
WHERE e1.employee_id = e2.employee_id);
This will produce exactly the same outcome as your query with RANK.
I think it would make sense to apply some sorting which is missing in your query. I don't know how you want to sort, but here an example to sort by the employee's name:
SELECT
employee_id,
first_name,
department_id,
salary
FROM employees e1
WHERE salary =
(SELECT MIN(e2.salary)
FROM employees e2
WHERE e1.employee_id = e2.employee_id)
ORDER BY first_name;
Since you asked for at least two solutions, let's have a look on another option:
SELECT
e1.employee_id,
e1.first_name,
e1.department_id,
e1.salary
FROM employees e1
JOIN (
SELECT employee_id, MIN(salary) salary
FROM employees
GROUP BY employee_id ) e2
ON e1.employee_id = e2.employee_id AND e1.salary = e2.salary
ORDER BY first_name;
As you can see, this differs since the sub query will apply a GROUP BY clause and it can be successfully executed as own query which is not possible for the sub query used in the previous query.
The JOIN to the main query will then make sure to get again the desired result.
Here are some options to get the employees with the minimum salary in their department:
With MIN (salary) OVER (...)
select employee_id, first_name, department_id, salary
from
(
select e.*, min(salary) over (partition by department_id) as min_sal
from employees e
)
where sal = min_sal;
With RANK and FETCH FIRST
select *
from employees
order by rank() over (partition by department_id order by salary)
fetch first row with ties;
With IN
select *
from employees
where (department_id, salary) in
(
select department_id, min(salary)
from employees
group by department_id
);
With NOT EXISTS
select *
from employees e
where not exists
(
select null
from employees other
where other.department_id = e.department_id
and other.salary < e.salary
);
If you will only ever have one person with the minimum salary per department then you can use KEEP:
SELECT department_id,
MIN(employee_id) KEEP (DENSE_RANK FIRST ORDER BY salary) AS employee_id,
MIN(first_name) KEEP (DENSE_RANK FIRST ORDER BY salary, employee_id) AS first_name,
MIN(salary) AS min_salary
FROM employees
GROUP BY department_id
Which, for the sample data:
CREATE TABLE employees (employee_id, department_id, first_name, salary) AS
SELECT 1, 1, 'Alice', 1000 FROM DUAL UNION ALL
SELECT 2, 1, 'Betty', 2000 FROM DUAL UNION ALL
SELECT 3, 2, 'Carol', 3000 FROM DUAL UNION ALL
SELECT 4, 2, 'Debra', 3000 FROM DUAL UNION ALL
SELECT 5, 2, 'Emily', 4000 FROM DUAL;
Outputs:
DEPARTMENT_ID
EMPLOYEE_ID
FIRST_NAME
MIN_SALARY
1
1
Alice
1000
2
3
Carol
3000
Note: this will not match Debra, even though she also has the lowest salary in department 2, as it will only find a single employee with the minimum salary and the minimum employee id.
If you can have multiple employees with the same minimum-per-department then you can use a correlated sub-query:
SELECT department_id,
employee_id,
first_name,
salary
FROM employees e
WHERE EXISTS(
SELECT 1
FROM employees x
WHERE e.department_id = x.department_id
HAVING MIN(x.salary) = e.salary
);
Which, for the sample data, outputs:
DEPARTMENT_ID
EMPLOYEE_ID
FIRST_NAME
SALARY
1
1
Alice
1000
2
3
Carol
3000
2
4
Debra
3000
Which does return Debra.
fiddle

SQL query for highest netpay among the employees

SQL query to display employees records whose netpay is greater than any other employee. Display name,salary,commission,netpay and manager name.
Exclude if manager name is null.
Netpay=salary+commission.
Table:
employees -- commission, dep_id, emp_id, emp_name, hire_date, job_name, manager_id, salary
This is my query:
SELECT e.emp_name, e.salary, e.commission, (e.salary+e.commission) AS netpay, x.emp_name
FROM employees e
INNER JOIN employees x ON x.emp_id = e.manager_id
WHERE (e.salary+e.commission) = (SELECT MAX(salary+commission) FROM employees where manager_id IS NOT NULL);
I'm not getting the test cases cleared for the above query. can any one help me out to solve this.
employee table
The output I'm getting is
emp_name
salary
commission
netpay
manager
Tucker
1600
7000
8600
blaze
I think this the max value among the records but still i have no idea whats the problem with this getting solved.
I am adding the create table and some insert commands:
CREATE TABLE employees(emp_id number, emp_name varchar2(25), job_name varchar2(25), manager_id number, hire_date date, salary number, commission number, dep_id number) ;
INSERT INTO employees VALUES(68319,'kayling', 'president', null, 11/18/1991,6000,3100,1001);
INSERT INTO employees VALUES(66928,'blaze', 'manager', 68319,5/1/1991,2750,3200,3001);
INSERT INTO employees VALUES(68454,'tucker', 'salesman', 66928,9/8/1991,1600,7000,3001);
INSERT INTO employees VALUES(65646,'clare', 'manager', 68319, 5/9/1991,2550,2300,1001);
INSERT INTO employees VALUES(67858,'scarlet', 'analyst', 65646, 1/11/1991,3100,520,2001);
Get the max value and then find the employee(s) matching that pay. ie:
SELECT e.emp_name, e.salary, e.commission, (e.salary+e.commission) AS netpay
FROM employees e
where (e.salary + +coalesce(e.commission,0)) = (Select max(salary+coalesce(commission,0)) from Employees where manager_id is not null) and
manager_id is not null;
EDIT: This is with manager name:
SELECT e.emp_name, e.salary, e.commission, (e.salary+e.commission) AS netpay, x.emp_name as Manager
FROM employees e
inner join employees x on e.manager_id = x.emp_id
where (e.salary + +coalesce(e.commission,0)) = (Select max(salary+coalesce(commission,0)) from Employees where manager_id is not null);
SQL fiddle link
You can use a hierarchical query to get the manager's name (without having read the table twice) and you can use FETCH FIRST ROW WITH TIES to get the people with the highest net salary:
SELECT emp_name,
salary,
commission,
salary + commission AS netpay,
PRIOR emp_name AS manager_name
FROM employees
WHERE manager_id IS NOT NULL
START WITH manager_id IS NULL
CONNECT BY PRIOR emp_id = manager_id
ORDER BY netpay DESC
FETCH FIRST ROW WITH TIES;
Which, for your sample data, outputs:
EMP_NAME | SALARY | COMMISSION | NETPAY | MANAGER_NAME
:--------| -----: | ---------: | -----: | :-----------
tucker | 1600 | 7000 | 8600 | blaze
db<>fiddle here
Select
e.emp_name as "name",
e.salary as "salary",
e.commission as "commission",
nvl2(e.commission,e.salary+e.commission,e.salary) as "netpay",
x.emp_name as "Manager name"
from (employees e inner join employees x on e.manager_id = x.emp_id)
where nvl2(e.commission,e.salary+e.commission,e.salary) > any (select nvl2(commission,salary+commission,salary) from employees) and x.emp_name is not null ;
SELECT
e.emp_name,
e.salary,
e.commission,
e.salary+e.commission NETPAY,
m.emp_name
FROM employees e
INNER JOIN employees m ON e.manager_id = m.emp_id
WHERE e.salary+e.commission > ANY (SELECT salary+commission FROM
employees) ;
You might be missing a trick here in the question asked. The expected result could be "greater than any other employee". This could also mean return those employees who have at least one employee having lesser netpay!! Late, but hope it helps! Enjoy!
select e1.emp_name,
e1.salary,
e1.commission,
e1.salary+e1.commission as netpay,
e2.emp_name as manager_name
from employees e1
join employees e2 on e1.manager_id = e2.emp_id
where e1.salary+e1.commission not in (
select min(salary+commission)
from employees where manager_id is not null);

correlated subquery on same table

here is table schema
emp(no, name, salary)
and insert data like this.
insert into emp values (1, 'gandalf', 3000);
insert into emp values (2, 'dobby', 4000);
insert into emp values (3, 'legolas', 5000);
and I select data, like this.
select no, name, salary
from emp e
where salary > (
select AVG(salary)
from emp
where no=e.no
);
but result is empty!!!
I don't understand...
I expected this
(3, 'legolas', 5000)
I try this query, It worked.
select no, name, salary
from emp
where salary > (
select AVG(salary)
from emp d
where no=d.no
);
So, Correlated Subquery must have to alias variable on same table?
At the same time, superquery must have not to alias variable?
and I don't understand this, too.
select no, name, salary
from emp s
where salary > (
select AVG(salary)
from emp d
where s.no=d.no
);
the result is empty, too..
why!!!????
Do not use correlation. At your example you are basically comparing employees salary with average salary (of the same person where s.no=d.no).
For instance for employee no = 1 you got:
WHERE 3000 > (3000) -- false no record returned
You probably want to pick employees which salary is higher than average of all employees. In that case use:
select no, name, salary
from emp
where salary > (
select AVG(salary)
from emp d
);
EDIT:
Scenario when to use correlation (deparment_id column added):
SELECT no, name, salary, deparment_id
FROM emp e1
WHERE salary >= (SELECT AVG(salary)
FROM emp e2
WHERE e1.department_id = e2.department_id);
Assuming that emp.no uniquely identifies each row, then the average in the correlated subquery is the salary for that employee. An employee's salary can never be greater than his/her salary. Presumably you intend:
select no, name, salary
from emp e
where salary > (select AVG(e2.salary)from emp e2);
You can also write this using window functions, though:
select no, name, salary
from (select e.*, avg(salary) over () as avg_salary
from emp e
) e
where salary > avg_salary;

Nested SQL Aggregate Function Query To Get Lowest Paid Employees Per Manager: My Query Is Not Working

The following is the prompt I'm trying to answer:
Show the manager number and the salary (only) of the lowest paid employee for that manager - labeled appropriately. Exclude salaries for employees whose manager is unknown. Exclude any groups here the minimum salary is less than $1000. List lowest salary first.
The following is my EMPLOYEES table:
create table EMPLOYEES
(EmpID char(4) unique Not null,
Ename varchar(10),
Job varchar(9),
MGR char(4),
Hiredate date,
Salary decimal(7,2),
Comm decimal(7,2),
DeptNo char(2) not null,
Primary key(EmpID),
Foreign key(DeptNo) REFERENCES DEPARTMENTS(DeptNo));
insert into EMPLOYEES values (7839,'King','President',null,'17-Nov-11',5000,null,10);
insert into EMPLOYEES values (7698,'Blake','Manager',7839,'01-May-11',2850,null,30);
insert into EMPLOYEES values (7782,'Clark','Manager',7839,'02-Jun-11',2450,null,10);
insert into EMPLOYEES values (7566,'Jones','Manager',7839,'02-Apr-11',2975,null,20);
insert into EMPLOYEES values (7654,'Martin','Salesman',7698,'28-Feb-12',1250,1400,30);
insert into EMPLOYEES values (7499,'Allen','Salesman',7698,'20-Feb-11',1600,300,30);
insert into EMPLOYEES values (7844,'Turner','Salesman',7698,'08-Sep-11',1500,0,30);
insert into EMPLOYEES values (7900,'James','Clerk',7698,'22-Feb-12',950,null,30);
insert into EMPLOYEES values (7521,'Ward','Salesman',7698,'22-Feb-12',1250,500,30);
insert into EMPLOYEES values (7902,'Ford','Analyst',7566,'03-Dec-11',3000,null,20);
insert into EMPLOYEES values (7369,'Smith','Clerk',7902,'17-Dec-10',800,null,20);
insert into EMPLOYEES values (7788,'Scott','Analyst',7566,'09-Dec-12',3000,null,20);
insert into EMPLOYEES values (7876,'Adams','Clerk',7788,'12-Jan-10',1100,null,20);
insert into EMPLOYEES values (7934,'Miller','Clerk',7782,'23-Jan-12',1300,null,10);
The following is my query:
select empid, salary
from employees
where salary in
(select MIN(salary)
from employees
where empid in
(select empid
from EMPLOYEES
where JOB != 'manager'))
order by Salary asc;
The result is only the lowest paid person other than a manager. I need the lowest paid worker per manager, including the lowest paid manager under the president.
select empid, salary
from employees
where salary in
(select MIN(salary)
from employees
where empid in
(select empid
from EMPLOYEES
where JOB != 'manager'
group by EmpID
)
and
JOB != 'manager' and MGR is not null
group by mgr
)
order by Salary asc;
Try this:
select MGR, MIN(salary)
from EMPLOYEES e
where Salary >= 1000 and MGR is not null
group by mgr
order by MIN(salary)
or perhaps:
select MGR, MIN(salary)
from EMPLOYEES e
where MGR is not null
group by mgr
having min(salary) >= 1000
order by MIN(salary)
The statement "Exclude any groups here the minimum salary is less than $1000" is not clear to me.

SQL: how to find maximum value items according a attribute

I am a beginner of SQL, having this table instructor:
ID name dept_name salary
001 A d01 1000
002 B d02 2000
003 C d01 3000
...
I am writing a code to find people who have highest salary in each department like:
name dept_name
C d01
B d02
I do know how to find maximum value
but I have no idea how to use it by according dept_name for all each department.
This will ensure that only records which are the highest salary for each department are returned to the result set.
SELECT name, dept_name, salary
FROM tbl t
WHERE NOT EXISTS(SELECT salary FROM tbl t2 WHERE t2.salary>t.salary AND t2.dept_name=t.dept_name)
Using SELECT name, MAX(salary) like other answerers have used won't work. Using MAX() will return the highest salary for each department, but the name will not necessarily be related to that salary value.
For example, SELECT MIN(salary), MAX(salary) is most likely going to pull values from different records. That's how aggregate functions work.
select name, max(dept_name)
from tbl
group by name
I assume it is a requirement to not include the salary in the result:
WITH INSTRUCTOR
AS
(
SELECT *
FROM (
VALUES ('001', 'A', 'd01', 1000),
('002', 'B', 'd02', 2000),
('003', 'C', 'd01', 3000)
) AS T (ID, name, dept_name, salary)
),
INSTRUCTOR_DEPT_HIGHEST_SALARY
AS
(
SELECT dept_name, MAX(salary) AS highest_salary
FROM INSTRUCTOR
GROUP
BY dept_name
)
SELECT ID, name, dept_name
FROM INSTRUCTOR AS T
WHERE EXISTS (
SELECT *
FROM INSTRUCTOR_DEPT_HIGHEST_SALARY AS H
WHERE H.dept_name = T.dept_name
AND H.salary = T.highest_salary
);
You can use the group by clause. Check this w3Schools link
SELECT NAME,DEPT_NAME,max(SALARY) FROM table_name group by DEPT_NAME