dense_rank() analytic function in oracle - sql

SELECT empno, deptno
dense_rank() OVER (PARTITION BY deptno
ORDER BY sal NULLS LAST) SRLNO
FROM emp
WHERE deptno IN (10, 20)
group by empno, deptno --,sal
ORDER BY deptno, SRLNO;
This Query didn't work because Sal should be in group by clause. Can anyone explain why it is so, and is there any alternative to get the rank in the same select without changing the group by clause? U want to order rank only based on salary.
EDIT
Suppose there is another column name commision in emp table and i have to group by deptno and commision and partition by deptno only ,So what will be the suggested answer :
SELECT empno, deptno,sum(sal)
dense_rank() OVER (PARTITION BY deptno
ORDER BY sal NULLS LAST) SRLNO
FROM emp
WHERE deptno IN (10, 20)
group by deptno,commision --,sal
ORDER BY deptno, SRLNO;
now how can i group by depno and commision and only partition by deptno.
HOW CAN I GROUP BY ON DIFFERENT COLUMN AND FIND RANK BASED ON PARTITION BY ON DIFFERNT COLUMN

Don't group by anything--your partition by does it for you!
More specifically, since you're referencing sal in your partition by and order by, group by needs to be aware of it to group the results bu that. However, in this case, you don't need the group by because your dense_rank is using its own partitioning from the partition by clause, and will not follow whatever is in group by.
Regarding your edit, use your over clause there:
sum(sal) over (partition by deptno, commission) as salsum

Related

SQL Group by, but allow duplicate if value is the same

I'm trying to group some max values by an ID (until here i got it figured out), and I also need to select the names from the persons with the max values, so my grouping should allow duplicates if two persons with that grouped ID have the same value (max).
Here's what I've got so far.
SELECT MGR,MAX(SAL) AS MaxSal
FROM EMP
WHERE MGR IS NOT NULL
GROUP BY MGR
Now I also need to extract the field ENAME, how could I do this, while grouping and also allowing duplicate groups when necessary ?
Starting Oracle 12c, one option uses window functions in the order by clause and a fetch clause:
select mgr, ename, sal
from emp
where mgr is not null
order by rank() over(partition by mgr order by sal desc)
fetch first row with ties
The solution is analytic functions. Here's how I achieved my desired result.
SELECT MGR,ENAME,SAL
FROM
(
SELECT MGR,ENAME,SAL,
MAX(SAL) OVER (PARTITION BY MGR) AS MaxSal
FROM EMP
)
WHERE SAL=MaxSal

How to calculate percentage in column?

I've got a task to calculate smallest salary in the departments and how many percents is it from total salary.
Also i can use only one select statement and probably I need to use window functions.
This is how i started:
SELECT distinct department_id,
min(salary) over(partition by department_id) as min_sal
FROM employees;
But if I use RATIO_TO_REPORT function it show all table, not grouped by department_id.
Sounds like
select min(salary) as min_salary, round( min(salary)/sum(salary) * 100 , 1 ) as percent
from employees
group by department_id
;
If you also need the employee's name who has the least salary in each department (assuming there's always only one), then add
, min(empl_name) keep (dense_rank first order by salary) as min_sal_empl_name
to the SELECT statement. If there can be ties for the least salary, and you need all the employees, please say so - in that case you probably need analytic functions as you guessed. Something like:
select empl_name, salary, round(salary/tot_sal*100, 1) as percent
from ( select empl_name, salary,
sum(salary) over (partition by department_id) as tot_sal,
rank() over (partition by department_id order by salary) as rn
from employees
)
where rn = 1
;
Added: OP indicated they need salary as percentage of total salary (across all departments) after all. This can be done by combining ratio_to_report() with empty windowing condition (no "partition by" anything) with rank() partitioned by department to pick up least salary in each department.
If you don't like over () for the windowing clause in ratio_to_report(), it can also be written as over (partition by null) to make it super-clear no partitioning is intended or needed.
The solution uses the EMP table in the SCOTT schema for testing, since the original post did not include sample data.
select deptno, empno, ename, sal, percent
from (
select empno, ename, sal, deptno,
round(100 * ratio_to_report(sal) over (), 1) as percent,
rank() over (partition by deptno order by sal) as rn
from scott.emp
)
where rn = 1
;
DEPTNO EMPNO ENAME SAL PERCENT
------ ----- ------ ---- -------
10 7934 MILLER 1300 4.5
20 7369 SMITH 800 2.8
30 7900 JAMES 950 3.3

How to get two highest salary in emp table?

BREAK ON DEPTNO SKIP 1
compute sum of sal on deptno
SELECT deptno, empno, ename,sal FROM
(SELECT deptno, empno, ename, sal FROM emp )
WHERE EXISTS (SELECT deptno FROM dept) order by 1,2 , sal desc ;
How can I get two highest sal from emp, and what is wrong with my code?
If you want all rows with the two highest distinct salaries in each department, then use dense_rank() as follows:
select deptno, empno, ename, sal
from (select e.*,
dense_rank() over (partition by deptno, order by sal desc) as seqnum
from emp e
) e
where seqnum <= 2
order by deptno, sal desc;
It looks like the question will be deleted, but it might as well have a correct answer.
It is not entirely clear what you want. In the title you say "two highest salary", but in the comment you mention something about a sum.
The following will show the two highest salaries. If there are multiple "highest" salaries, all will be shown
select deptno, empno, ename, sal
from (
SELECT deptno, empno, ename, sal,
dense_rank() over (order by sal desc) as rnk
FROM emp
)
where rnk <= 2
order by sal desc;
To get this per department, you can use this:
select deptno, dept_salary
from (
select deptno, dept_salary,
dense_rank() over (order by dept_salary desc) as rnk
from (
SELECT deptno, sum(sal) as dept_salary
FROM emp
group by deptno
) t1
) t2
where rnk <= 2
order by dept_salary desc
Simple query actually:
SELECT deptno, empno, ename,sal FROM emp eb
WHERE (deptno, empno) IN
(SELECT depno, empno FROM
(SELECT deptno, empno FROM emp ei
WHERE ei.deptno = eb.deptno
ORDER BY ei.sal DESC
) WHERE rownum <= 2
);
The last WHERE rownum <=2 differ from SQL to SQL, in Mysql you would need LIMIT 2, in MSSQL Server, you would need to do SELECT TOP 2, in Oracle WHERE rownum <= 2. Depends on the engine you use.

Display columns based on sub query

I want to display the name of department with highest SUM of salary.
I am using oracle sql and the table structure is Dept(Deptno,Dname,Loc) and Emp(Empno,Ename,Job,Salary,Deptno).
The query I use was
select Dname
from Dept
where Deptno=
( select Deptno
from Emp
where rownum=1
group by Deptno
order by sum(Salary) Desc
);
This gives an error:
Right parenthesis missing.
When I run the sub-query alone, it successfully returns a Deptno. But with the parent query, I get the above error.
What is the problem and what can be the possible solution?
select dname
from (select dname, rank() over (order by sum(salary) desc) rnk
from dept d
inner join emp e
on e.deptno = d.deptno
group by dname, e.deptno
)
where rnk = 1;
note, in your example putting where rownum=1 where you did is a huge bug. it would mean pick 1 random row and sort it (not really the highest salary row..just any old row)
my solution may get over 1 row if 2 deptartments have the same highest salary. you can use row_number() instead of rank() to just pick one if you want.

Combining columns

I am trying to do the following:
I have a table with ename, job, deptno, and sal. I am trying to initiate a query that returns the top earners of each department. I have done this with grouping and a subquery. However, I also want to display the average sal by deptno. So the following would be the result:
"ename" "dept" "sal" "average of dept"
sal 20 1000 500
kelly 30 2000 800
mika 40 3000 400
this might be impossible since the average does not associate with the other rows.
any suggestion would be appreciated. Thanks. I am using Oracle 10g to run my queries.
You could use analytic functions:
WITH RankedAndAveraged AS (
SELECT
ename,
dept,
sal,
RANK() OVER (PARTITION BY dept ORDER BY sal DESC) AS rnk,
AVG(sal) OVER (PARTITION BY dept) AS "average of dept"
FROM atable
)
SELECT
ename,
dept,
sal,
"average of dept"
FROM RankedAndAveraged
WHERE rnk = 1
This may return more than one employee per department if all of them have the same maximum value of sal. You can replace RANK() with ROW_NUMBER() if you only want one person per department (in which case you could also further extend ORDER BY by specifying additional sorting criteria to pick the top item, otherwise it will be picked randomly from among those with the maximum salary).
This should work. The only trick is that if you have several employees with the maximum salary in a department, it will show all of them.
SELECT t.ename, t.deptno, mx.sal as sal, mx.avg_sal as avg_sal
FROM tbl t,
(SELECT MAX(sal) AS sal, AVG(sal) AS avg_sal, deptno
FROM tbl
GROUP BY deptno) mx
WHERE t.deptno = mx.deptno AND t.sal = mx.sal
Not sure about Oracle, haven't used it in about 10 years, but something like this should be possible:
SELECT
ename, deptno, sal,
(SELECT AVG(T2.sal)
FROM tbl T2
WHERE T2.deptno = T.deptno
) AS average_of_dept
FROM tbl T
GROUP BY deptno
HAVING sal = MAX(sal)