How to calculate percentage in column? - sql

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

Related

How to get distinct column with max value and limit, hive

I have a table emp with around 10 rows
empno,ename,dept,sal,deptno
now I want result as ename from each dept who as max sal.
EX:
12,ravi,manager,20000,10
43,hari,engineer,10000,20
32,sam,clerk,5000,30
Get the max salary for each dept and then get the ename for each max salary.
select ename,dept,sal
from emp
where sal in (select max(sal) as sal from emp group by dept)
Using row_number() and partition
select * from
(
select ename,dept,sal, row_number() over (partition by dept order by sal desc) as row_no
from emp
) res
where res.row_no = 1

SQL select rows without duplicates

Practicing some SQL, we have to get the name of the employees whose salary is the greatest of his department. But if in any department there were more than one employer with the greatest salary, we would not have to consider that department.
We got the first part but not the second one (because there are two employees with the same greatest salary (3,000) in the same department (20)).
This is what we did:
SQL> SELECT ename, sal, deptno FROM emp a
WHERE sal >= ALL (SELECT sal FROM emp WHERE deptno=a.deptno)
ORDER BY sal;
And this is what we got:
ENAME SAL DEPTNO
---------- ------- ------
BLAKE 2,850 30
FORD 3,000 20
SCOTT 3,000 20
KING 5,000 10
4 filas seleccionadas.
Any help will be useful, thank you!
SELECT ename, sal, deptno
FROM emp a
WHERE not exists (
SELECT *
FROM emp
WHERE deptno=a.deptno
and sal >= a.sal
and ename != a.ename)
ORDER BY sal;
with cte as
( SELECT ename, sal, deptno
, row_number() over (partition by deptno order by sal desc) as rn
FROM emp
)
select ename, sal, deptno from cte where rn = 1
except
select ename, sal, deptno from cte where rn = 2
order by sal
if this does not work in oracle - it used to be also tagged mssql
You can have what you need with some analytic functions:
select ename,
deptno,
sal
from (
select ename,
deptno,
sal,
row_number() over(partition by deptno order by sal desc) AS num,
count(1) over(partition by deptno, sal) AS count
from emp
)
where num = 1
and count = 1
The inner query orders by salary and counts the number of employees with the same salary in the same department; the outer one simply filters for employees with the maximum salary, where only one employee has that salary in the department.
With a different approach, simply modifying your query, you can try:
SELECT ename, sal, deptno FROM emp a
WHERE sal >= ALL (SELECT sal FROM emp WHERE deptno=a.deptno)
and (select count(1) from emp b where a.deptno = b.deptno and a.sal = b.sal) = 1
The first way gives better performance, with a single table scan, while the second one needs a nested query, thus being less efficient
try to use GROUP BY column_name`, it will show the record without duplicate.

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.

How can I select the record with the 2nd highest salary in database Oracle?

Suppose I have a table employee with id, user_name, salary. How can I select the record with the 2nd highest salary in Oracle?
I googled it, find this solution, is the following right?:
select sal from
(select rownum n,a.* from
( select distinct sal from emp order by sal desc) a)
where n = 2;
RANK and DENSE_RANK have already been suggested - depending on your requirements, you might also consider ROW_NUMBER():
select * from (
select e.*, row_number() over (order by sal desc) rn from emp e
)
where rn = 2;
The difference between RANK(), DENSE_RANK() and ROW_NUMBER() boils down to:
ROW_NUMBER() always generates a unique ranking; if the ORDER BY clause cannot distinguish between two rows, it will still give them different rankings (randomly)
RANK() and DENSE_RANK() will give the same ranking to rows that cannot be distinguished by the ORDER BY clause
DENSE_RANK() will always generate a contiguous sequence of ranks (1,2,3,...), whereas RANK() will leave gaps after two or more rows with the same rank (think "Olympic Games": if two athletes win the gold medal, there is no second place, only third)
So, if you only want one employee (even if there are several with the 2nd highest salary), I'd recommend ROW_NUMBER().
If you're using Oracle 8+, you can use the RANK() or DENSE_RANK() functions like so
SELECT *
FROM (
SELECT some_column,
rank() over (order by your_sort_column desc) as row_rank
) t
WHERE row_rank = 2;
This query works in SQL*PLUS to find out the 2nd Highest Salary -
SELECT * FROM EMP
WHERE SAL = (SELECT MAX(SAL) FROM EMP
WHERE SAL < (SELECT MAX(SAL) FROM EMP));
This is double sub-query.
I hope this helps you..
WITH records
AS
(
SELECT id, user_name, salary,
DENSE_RANK() OVER (PARTITION BY id ORDER BY salary DESC) rn
FROM tableName
)
SELECT id, user_name, salary
FROM records
WHERE rn = 2
DENSE_RANK()
You should use something like this:
SELECT *
FROM (select salary2.*, rownum rnum from
(select * from salary ORDER BY salary_amount DESC) salary2
where rownum <= 2 )
WHERE rnum >= 2;
select * from emp where sal=(select max(sal) from emp where sal<(select max(sal) from emp))
so in our emp table(default provided by oracle) here is the output
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
7698 BLAKE MANAGER 7839 01-MAY-81 3000 30
7788 SCOTT ANALYST 7566 19-APR-87 3000 20
7902 FORD ANALYST 7566 03-DEC-81 3000 20
or just you want 2nd maximum salary to be displayed
select max(sal) from emp where sal<(select max(sal) from emp)
MAX(SAL)
3000
select * FROM (
select EmployeeID, Salary
, dense_rank() over (order by Salary DESC) ranking
from Employee
)
WHERE ranking = 2;
dense_rank() is used for the salary has to be same.So it give the proper output instead of using rank().
select Max(Salary) as SecondHighestSalary from Employee where Salary not in
(select max(Salary) from Employee)
I would suggest following two ways to implement this in Oracle.
Using Sub-query:
select distinct SALARY
from EMPLOYEE e1
where 1=(select count(DISTINCT e2.SALARY) from EMPLOYEE e2 where
e2.SALARY>e1.SALARY);
This is very simple query to get required output. However, this query is quite slow as each salary in inner query is compared with all distinct salaries.
Using DENSE_RANK():
select distinct SALARY
from
(
select e1.*, DENSE_RANK () OVER (order by SALARY desc) as RN
from EMPLOYEE e
) E
where E.RN=2;
This is very efficient query. It works well with DENSE_RANK() which assigns consecutive ranks unlike RANK() which assigns next rank depending on row number which is like olympic medaling.
Difference between RANK() and DENSE_RANK():
https://oracle-base.com/articles/misc/rank-dense-rank-first-last-analytic-functions
I believe this will accomplish the same result, without a subquery or a ranking function:
SELECT *
FROM emp
ORDER BY sal DESC
LIMIT 1
OFFSET 2
This query helps me every time for problems like this. Replace N with position..
select *
from(
select *
from (select * from TABLE_NAME order by SALARY_COLUMN desc)
where rownum <=N
)
where SALARY_COLUMN <= all(
select SALARY_COLUMN
from (select * from TABLE_NAME order by SALARY_COLUMN desc)
where rownum <=N
);
select * from emp where sal = (
select sal from
(select rownum n,a.sal from
( select distinct sal from emp order by sal desc) a)
where n = 2);
This is more optimum, it suits all scenarios...
select max(Salary) from EmployeeTest where Salary < ( select max(Salary) from EmployeeTest ) ;
this will work for all DBs.
You can use two max function. Let's say get data of userid=10 and its 2nd highest salary from SALARY_TBL.
select max(salary) from SALARY_TBL
where
userid=10
salary <> (select max(salary) from SALARY_TBL where userid=10)
Replace N with your Highest Number
SELECT *
FROM Employee Emp1
WHERE (N-1) = (
SELECT COUNT(DISTINCT(Emp2.Salary))
FROM Employee Emp2
WHERE Emp2.Salary > Emp1.Salary)
Explanation
The query above can be quite confusing if you have not seen anything like it before – the inner query is what’s called a correlated sub-query because the inner query (the subquery) uses a value from the outer query (in this case the Emp1 table) in it’s WHERE clause.
And Source
I have given the answer here
By the way I am flagging this Question as Duplicate.
Syntax it for Sql server
SELECT MAX(Salary) as 'Salary' from EmployeeDetails
where Salary NOT IN
(
SELECT TOP n-1 (SALARY) from EmployeeDetails ORDER BY Salary Desc
)
To get 2nd highest salary of employee then we need replace “n” with 2 our query like will be this
SELECT MAX(Salary) as 'Salary' from EmployeeDetails
where Salary NOT IN
(
SELECT TOP 1 (SALARY) from EmployeeDetails ORDER BY Salary Desc
)
3rd highest salary of employee
SELECT MAX(Salary) as 'Salary' from EmployeeDetails
where Salary NOT IN
(
SELECT TOP 2 (SALARY) from EmployeeDetails ORDER BY Salary Desc
)
SELECT * FROM EMP WHERE SAL=(SELECT MAX(SAL) FROM EMP WHERE SAL<(SELECT MAX(SAL) FROM EMP));
(OR)
SELECT ENAME ,SAL FROM EMP ORDER BY SAL DESC;
(OR)
SELECT * FROM(SELECT ENAME,SAL ,DENSE_RANK()
OVER(PARTITION BY DEPTNO ORDER BY SAL DESC) R FROM EMP) WHERE R=2;
select salary from EmployeeDetails order by salary desc limit 1 offset (n-1).
If you want to find 2nd highest than replace n with that 2.

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)