Analytic functions and means of window clause for calculating sum - sql

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 a user, I need the sum of salary of 3 employees with highest salary in each department. I have done query for defining 3 employees with highest salary in each department:
SELECT
*
FROM
(
SELECT
employee_id,
first_name
|| ' '
|| last_name,
department_id,
salary,
ROW_NUMBER()
OVER(PARTITION BY department_id
ORDER BY
salary DESC
--ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING
) result
FROM
employees
)
WHERE
result <= 3;
I need to use means of window clause. I have done something like this:
SELECT
department_id,
SUM(salary)
OVER (PARTITION BY department_id ORDER BY salary
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) total_sal
FROM
(
SELECT
employee_id,
first_name
|| ' '
|| last_name,
department_id,
salary,
ROW_NUMBER()
OVER(PARTITION BY department_id
ORDER BY
salary DESC
--ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING
) result
FROM
employees
)
WHERE
result <= 3;
Here is the result:
It has the necessary sum for 3 people in department and other unnnecessary results for 2 and so on. I need such result:
How can I modify my query to receive appropriate result (I need to use a window clause and analytic fuctions)?

You want aggregation rather than windowing in the outer query:
SELECT
department_id,
SUM(salary) total_sal
FROM
(
SELECT
employee_id,
first_name
|| ' '
|| last_name,
department_id,
salary,
ROW_NUMBER()
OVER(PARTITION BY department_id
ORDER BY
salary DESC
--ROWS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING
) result
FROM
employees
) e
WHERE
result <= 3
GROUP BY department_id
I we were to do the same task with window functions only, then, starting from the existing query, we can either add another level of nesting of some sort, or use WITH TIES. Both pursue the same effect, which is to limit the results to one row per group.
The latter would look like:
SELECT
department_id,
SUM(salary) OVER(PARTITION BY department_id) total_sal
FROM (
SELECT e.*,
ROW_NUMBER() OVER(PARTITION BY department_id ORDER BY salary DESC) result
FROM employees e
) e
WHERE result <= 3
ORDER BY result FETCH FIRST ROW WITH TIES
While the former would phrase as:
SELECT department_id, total_sal
FROM (
SELECT e.*,
SUM(salary) OVER(PARTITION BY department_id) total_sal
FROM (
SELECT e.*,
ROW_NUMBER() OVER(PARTITION BY department_id ORDER BY salary DESC) result
FROM employees e
) e
WHERE result <= 3
) e
where result = 1

Related

Analytic query trying to solve

im solving the following task with analytic functions and im stuck.
task: Write a query that shows the latest hired employee per department. In case of ties, use the lowest employee ID.
select a.EMPLOYEE_ID,
a.DEPARTMENT_ID,
a.FIRST_NAME,
a.LAST_NAME,
a.HIRE_DATE,
a.JOB_ID
from (select ROW_NUMBER() over (PARTITION by department_id order by hire_date desc)
from hr.EMPLOYEES a) A
where A = 1 ;
You need to include the columns you want to select in the outer query in the SELECT clause of the inner query and need to give an alias to the ROW_NUMBER computed value:
select EMPLOYEE_ID,
DEPARTMENT_ID,
FIRST_NAME,
LAST_NAME,
HIRE_DATE,
JOB_ID
from (
select EMPLOYEE_ID,
DEPARTMENT_ID,
FIRST_NAME,
LAST_NAME,
HIRE_DATE,
JOB_ID,
ROW_NUMBER() over (PARTITION by department_id order by hire_date desc) AS rn
from hr.EMPLOYEES
)
where rn = 1 ;
You still need to address the second part of the question:
In case of ties, use the lowest employee ID.
However, since this appears to be a homework question, I'll leave that for you to solve.

SQL how can i detect if a value decrease over time?

Hi, how can i check who are the employees whose salary has fallen ?
:
SELECT employees.emp_no, first_name, last_name, salary, from_date, to_date, hire_date
from employees
INNER JOIN salaries ON employees.emp_no = salaries.emp_no;
I only want to fetch the name of employees whose salary has fallen
You can use the positional analytic function LAG() to find these rows. This is a standard SQL function that peeks at a previous row, according to a specific criteria.
For example:
select emp_no, first_name, last_name
from (
select
e.*,
s.salary,
lag(s.salary) over(partition by e.emp_no order by from_date) as prev_salary
from employees e
join salaries s on s.emp_no = e.emp_no
) x
where salary < prev_salary
You should look into using windowing functions. It should look something like this:
with salary as (
SELECT employees.emp_no, concat(first_name, " ",last_name) as emp, salary, coalesce(hire_date, from_date) as from_date, to_date
from employees
INNER JOIN salaries ON employees.emp_no = salaries.emp_no
), last_sal as (
select emp_no, emp, salary, to_date, lag(salary) over (partition by emp_no, order by to_date) as last_salary
from salary
)
select *
from last_sal
where salary < last_salary
a windowing function basically takes a look at a subset of the data. In this case, the subset is of each employee, and then that window is ordered by to_date. Lag tells it to look backwards, and effectively produces a row which has the prior row's salary result next to the current row for the other columns.

Oracle SQL how to find a employee whose department's code is null

SELECT ID user_id, NAME user_name, SALARY user_salary, DEPARTMENT user_dept_code
FROM EMPTABLE
WHERE (DEPARTMENT, SALARY) IN (
SELECT DEPARTMENT, MAX(SALARY)
FROM EMPTABLE
GROUP BY DEPARTMENT
)
ORDER BY DEPARTMENT;
The result of the code above doesn't contain any employee whose DEPARTMENT is null.
How can I make them in the result too?
You seem to want the employee(s) with the greatest salary in each department - presumably, you want a group for employees that have no department as well.
I would recommend window functions:
select *
from (
select e.*,
rank() over(partition by deptcode order by sal desc) rn
from emp e
) e
where rn = 1
order by deptcode
For oracle, you may try to use NVL2 :
SELECT EMPID, EMPNAME, DEPTCODE, SAL
FROM EMP
WHERE (NVL2(DEPTCODE, DEPTCODE, ''), SAL) IN (
SELECT NVL2(DEPTCODE, DEPTCODE, ''), MAX(SAL)
FROM EMP
GROUP BY DEPTCODE
)
ORDER BY DEPTCODE;

why query is showing 'invalid relational operator'

select *
from employees
where department_id,salary in (
select department_id,max(salary)
from employees group by department_id
)
You want tuple comparison - you need to surround the tuple of columns on the left side of in with parentheses:
select *
from employees
where (department_id,salary) in (
select department_id, max(salary) from employees group by department_id
)
Note that this top-1-per-group query can be more efficiently phrased with window functions:
select *
from (
select e.*, rank() over(partition by department_id order by salary desc nulls last) rn
from employees e
) t
where rn = 1

Oracle SQL - Get name from table based on group by

I have a table called employee and has columns as follows
emp_id number
emp_name varchar(30)
salary float
dept_id number
I want to get the output as any one name of employee within that department and employee count from each department. I tried the below, but didn't work well
SELECT emp_name, count(*) FROM emp
GROUP BY dept_id, emp_name;
Expected output:
emp_name, count(*)
abc, 4
def, 2
xyz, 10
Can anyone suggest?
You can try this if you want just a basic "random employee" shown for each department.
select emp_name, emp_count
from (
select emp_name, dept_id,
count(*) over (partition by dept_id) emp_count,
row_number() over (partition by dept_id
order by dbms_random.value ) rnum
from employee
)
where rnum = 1
/
This uses analytic function to calculate the counts, and then pick off 1 random row to display.