Analytic query trying to solve - sql

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.

Related

Analytic functions and means of window clause for calculating sum

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

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.

Ora-00935 error group nested too deeply

The following sql query keeps throwing an error that group function is nested too deeply
select department_name
from department
where department_id in (select department_id
from student
group by department_id
having count(student_id)=max(count(student_id)));
Can someone suggest how to correct this without making other table ??
I do not see what the in buys you. I find this easier to write and to follow with the calculation in the from clause:
select d.department_name
from (select s.department_id, count(*) as num_students,
rank() over (order by count(*) desc) as seqnum
from student s
group by s.department_id
) s join
department d
on s.department_id = d.department_id
where seqnum = 1;
This also makes it easy to add the number of students in the most populous departments.
Try using the MAX analytics function
SELECT department_name
FROM department
WHERE department_id IN
( SELECT
department_id
FROM
( SELECT
department_id,
COUNT(student_id) students,
MAX(COUNT(student_id)) OVER () max_students
FROM student
GROUP BY department_id
)
WHERE students=max_students
);
Not tested.
Or in Oracle 12c onwards you could use the FECTH clause
SELECT department_name
FROM department
WHERE department_id IN
( SELECT department_id
FROM student
GROUP BY department_id
ORDER BY COUNT(student_id) DESC
FETCH FIRST ROW WITH TIES
);

What's wrong with my simple SQL query?

What is wrong with this SQL query?
SELECT
department_id, MAX(AVG(SALARY))
FROM
EMPLOYEES
GROUP BY
department_id;
It shows not a single-group group function
2 Aggregate functions in one Query can not be done, you should use a Subquery to achieve your result.
I've not possibility to test it right now so no guarantees on this query but you may get an idea.
select max (avg_salary)
from (select department_id, avg(SALARY) AS avg_salary
from EMPLOYEES
group by department_id);
The inner query selects deparment_id and average salary.
Avarage salary is selected using the alias avg_salary using the AS statement.
The outer query selects the maximum of avg_salary-
That's maybe not a complete solution to your problem and as I said, not tested so no guarantees, but you should have an idea now how to start. ;-)
You cant have more than one aggregate functions in one query. try this one
select dept, max(average) over (partition by dept)
from (SELECT department_id dept,
(AVG(SALary) OVER (PARTITION BY department_id)) average
FROM employees);
Alternative 1, double GROUP BY:
SELECT department_id, AVG(SALARY)
FROM EMPLOYEES
GROUP BY department_id
HAVING AVG(SALARY) = (select max(avg_sal)
from (select avg(salary) as avg_sal
from EMPLOYEES
group by department_id))
Will return both department_id's if there's a tie!
Alternative 2, use a cte (common table expression):
with
(
SELECT department_id, AVG(SALARY) as avg_sal
FROM EMPLOYEES
GROUP BY department_id
) as cte
select department_id, avg_sal
from cte
where avg_sal = (select max(avg_sal) from cte)
This too will return both department_id's if there's a tie!

MAX and GROUP BY - SQL

I'm working on Oracle SQL and HR database, I'm trying to select maximum salary in department like this:
SELECT MAX(salary), department_id
FROM employees GROUP BY department_id;
It works fine, but I want to know >who< is earning the most, so I simply change query this way:
SELECT first_name, last_name, MAX(salary), department_id
FROM employees GROUP BY department_id;
And it's wrong. Could you help me, please?
The most efficient way to do this sort of analysis is generally to use analytic functions (window functions). Something like
SELECT first_name,
last_name,
salary,
department_id
FROM (SELECT e.*,
rank() over (partition by department_id
order by salary desc) rnk
FROM employees e)
WHERE rnk = 1
Depending on how you want to handle ties (if two people in the department are tied for the maximum salary, for example, do you want both people returned or do you want to return one of the two arbitrarily), you may want to use the row_number or dense_rank functions rather than rank.
Create a view:
create view max_salary as select max(salary), department_id from employees group by department_id
Then create a query:
select first_name, last_name, salary, department_id
from employees a, max_salary b
where a.department_id = b.department_id
and a.salary = b.salary