how to retrieve highest and lowest salary from following table? - sql

I have employee table
EMP_ID | F_NAME | L_NAME | SALARY | JOINING_DATE | DEPARTMENT
-----------------------------------------------------------------------------------
101 | John | Abraham | 100000 | 01-JAN-14 09.15.00.000000 AM | Banking
102 | Michel | Clarke | 800000 | | Insaurance
102 | Roy | Thomas | 70000 | 01-FEB-13 12.30.00.000000 PM | Banking
103 | Tom | Jose | 600000 | 03-FEB-14 01.30.00.000000 AM | Insaurance
105 | Jerry | Pinto | 650000 | 01-FEB-13 12.00.00.000000 PM | Services
106 | Philip | Mathew | 750000 | 01-JAN-13 02.00.00.000000 AM | Services
107 | TestName1 | 123 | 650000 | 01-JAN-13 12.05.00.000000 PM | Services
108 | TestName2 | Lname% | 600000 | 01-JAN-13 12.00.00.000000 PM | Insaurance
i want to find highest and lowest salary from above table in oracle sql.
if i do
select max(salary) from (select * from (select salary from employee) where rownum <2);
it returns MAX(SALARY) = 100000 where it should return 800000
If I do
select max(salary)
from (select * from (select salary from employee)
where rownum <3);
it returns MAX(SALARY) = 800000
If I do
select min(salary)
from (select * from(select salary from employee)
where rownum < 2);
it will return MIN(SALARY) = 100000 where it should return 70000.
What is wrong in this query?
what should be the correct query?

You don't need all these subqueries:
SELECT MAX(salary), MIN(salary)
FROM employee

SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE employee ( EMP_ID, F_NAME, L_NAME, SALARY, JOINING_DATE, DEPARTMENT ) AS
SELECT 101, 'John', 'Abraham', 100000, TIMESTAMP '2014-01-01 09:15:00', 'Banking' FROM DUAL
UNION ALL SELECT 102, 'Michel', 'Clarke', 800000, NULL, 'Insurance' FROM DUAL
UNION ALL SELECT 102, 'Roy', 'Thomas', 70000, TIMESTAMP '2013-02-01 12:30:00', 'Banking' FROM DUAL
UNION ALL SELECT 103, 'Tom', 'Jose', 600000, TIMESTAMP '2014-02-03 01:30:00', 'Insurance' FROM DUAL
UNION ALL SELECT 105, 'Jerry', 'Pinto', 650000, TIMESTAMP '2013-02-01 12:00:00', 'Services' FROM DUAL
UNION ALL SELECT 106, 'Philip', 'Mathew', 750000, TIMESTAMP '2013-01-01 02:00:00', 'Services' FROM DUAL
UNION ALL SELECT 107, 'TestName1', '123', 650000, TIMESTAMP '2013-01-01 12:05:00', 'Services' FROM DUAL
UNION ALL SELECT 108, 'TestName2', 'Lname%', 600000, TIMESTAMP '2013-01-01 12:00:00', 'Insurance' FROM DUAL;
Query 1 - To find the highest-n salaries:
SELECT *
FROM (
SELECT salary
FROM employee
ORDER BY salary DESC
)
WHERE rownum <= 3 -- replace with the number of salaries you want to retrieve.
Results:
| SALARY |
|--------|
| 800000 |
| 750000 |
| 650000 |
Query 2 - To find the lowest-n salaries:
SELECT *
FROM (
SELECT salary
FROM employee
ORDER BY salary ASC
)
WHERE rownum <= 3 -- replace with the number of salaries you want to retrieve.
Results:
| SALARY |
|--------|
| 70000 |
| 100000 |
| 600000 |

For each row returned by a query, the ROWNUM pseudocolumn returns a number indicating the order in which Oracle selects the row from a table or set of joined rows. The first row selected has a ROWNUM of 1, the second has 2, and so on.
So in your case :
select max(salary) from (select * from (select salary from employee) where rownum <2);
This query will return
101 John Abraham 100000 01-JAN-14 09.15.00.000000 AM Banking
only this row as output... and hence the max value will be 100000 only.
select max(salary) from (select * from (select salary from employee) where rownum <3);
This query will tak first 2 rows from your table, i.e.,
101 John Abraham 100000 01-JAN-14 09.15.00.000000 AM Banking
102 Michel Clarke 800000 Insaurance
and hence the max salary will be 800000.
Similarly,
select min(salary)from (select * from(select salary from employee)where rownum<2);
will only select 1st row
select min(salary)from (select * from(select salary from employee)where rownum<2);
so min salary will be 100000.
P.S. : You could simply write your queries like this :
select max(salary) from employee where rownum<[n];
where n will be ROWNUM to which you want to limit the number of rows returned by your query

Try it:
select *
from (
select T.*, rownum RRN
from (
select salary
from employee
order by salary desc) T)
where RRN < 3

so you want the 2nd highest and 2nd lowest salary? Check this out
select max(salary), min(salary) from employee
where salary < (select max(salary) from employee)
and salary > (select min(salary) from employee)
;

1) For lowest salary.
select * from (
select empno,job,ename,sal
from emp order by sal)
where rownum=1;
2) For Highest salary.
select * from (
select empno,job,ename,sal
from emp order by sal desc)
where rownum=1;

i don't know why you make complicated queries you can simply write this and get the same result:
select salary
from employees
where rownum <=3
order by salary desc;

you can solve your problem with following queries:
Highest salary:
Select * from Employee(Select salary from Employee ORDER BY salary DISC) where rownum=1;
Lowest salary:
Select * from Employee(Select salary from Employee ORDER BY salary) where rownum=1;
Second highest salary:
Select MAX(Salary) from Employee
Where Salary < (Select MAX(Salary) from employee);
Second Lowest salary :
Select MIN(Salary) from Employee
Where Salary > (Select MIN(Salary) from employee);

Related

List the branch that monthly pays the most in salaries

I have this table, the expected output should be B003 since it's pays 54,000
STAFF
SALARY
BRAN
SL21
30000
B005
SG37
12000
B003
SG14
18000
B003
SA9
9000
B007
SG5
24000
B003
SL41
9000
B005
So far I only have this subquery, which isn't working how I expected.
SELECT BRANCHNO
FROM STAFF
WHERE (SALARY) IN (SELECT MAX(SUM(SALARY))
FROM STAFF
GROUP BY BRANCHNO);
This works but I want a subquery that returns the branchno
SELECT MAX(SUM(SALARY))
FROM STAFF
GROUP BY BRANCHNO;
select BRANCHNO max(sum_sal)
from (SELECT BRANCHNO, SUM(SALARY) sum_sal
FROM STAFF
GROUP BY BRANCHNO) q1
group by BRANCHNO ;
The column used to group the rows can be displayed. So, add BRANCHNO to your select clause.
One option is to use rank analytic function which ranks branches by sum of their salaries in descending order; you'd then return the one(s) that rank as the highest (rnk = 1).
Sample data:
SQL> with staff (staff, salary, bran) as
2 (select 'SL21', 30000, 'B005' from dual union all
3 select 'SG37', 12000, 'B003' from dual union all
4 select 'SG14', 18000, 'B003' from dual union all
5 select 'SA9' , 9000, 'B007' from dual union all
6 select 'SG5' , 24000, 'B003' from dual union all
7 select 'SL41', 9000, 'B005' from dual
8 )
Query:
9 select bran
10 from (select bran, rank() over (order by sum(salary) desc) rnk
11 from staff
12 group by bran
13 )
14 where rnk = 1;
BRAN
----
B003
SQL>

Need sql query to get expected output for the below,

Tried the below query, but it works only for the first and second records.
Select
name,
dept,
sal,
(
coalesce(sal, 0) + coalesce(saltodrop)
) as running total (
SELECT
name,
dept,
sal,
LAG(Sal, 1, 0) OVER(
PARTITION BY [dept]
ORDER BY
[name],
[dept] ASC
) AS [saltodrop]
FROM
dataset
) as data_set_extract
Input
Name dept sal
John sales 10000
Tom sales 8000
Tim sales 5000
George finance 6000
Dane finance 4000
Mike hr 5000
Meme hr 6000
Ark it 5000
Output
Name dept sal
John sales 1000
Tom sales 18000
Tim sales 23000
George finance 29000
Dane finance 33000
Mike hr 38000
Meme hr 44000
Ark it 49000
Using the Oracle database, I need to add a consecutive row of the
first two records, later the sum of the first record and second record
with that of the third record and so on.
Assuming you have already ordered the results then use the SUM analytic function and order by ROWNUM to keep the current ordering:
SELECT t.*,
SUM(sal) OVER (ORDER BY ROWNUM) AS cumulative_sal
FROM table_name t;
Which, for the sample data:
CREATE TABLE table_name (Name, dept, sal) AS
SELECT 'John', 'sales', 10000 FROM DUAL UNION ALL
SELECT 'Tom', 'sales', 8000 FROM DUAL UNION ALL
SELECT 'Tim', 'sales', 5000 FROM DUAL UNION ALL
SELECT 'George', 'finance', 6000 FROM DUAL UNION ALL
SELECT 'Dane', 'finance', 4000 FROM DUAL UNION ALL
SELECT 'Mike', 'hr', 5000 FROM DUAL UNION ALL
SELECT 'Meme', 'hr', 6000 FROM DUAL UNION ALL
SELECT 'Ark', 'it', 5000 FROM DUAL;
Outputs:
NAME
DEPT
SAL
CUMULATIVE_SAL
John
sales
10000
10000
Tom
sales
8000
18000
Tim
sales
5000
23000
George
finance
6000
29000
Dane
finance
4000
33000
Mike
hr
5000
38000
Meme
hr
6000
44000
Ark
it
5000
49000
db<>fiddle here

Oracle SQL - Scanning attribute Changes

I have the following employee table
EMPID RECORD_DATE DEPARTMENT
123456 2020-01-01 HR
123456 2020-02-01 HR
123456 2020-03-01 FINANCE
123456 2020-04-01 FINANCE
987654 2020-01-01 HR
987654 2020-02-01 HR
987654 2020-03-01 HR
987654 2020-04-01 LEGAL
Using Oracle PL/SQL, I need to build an expression to ascertain a list of employee movement, specifically that have moved from HR to any other (non-HR) department.
Expected result:
EMPID MOVEMENT_DATE DEPT_BEFORE DEPT_AFTER
123456 2020-03-01 HR FINANCE
987654 2020-04-01 HR LEGAL
I know you can use the Lead or Lag function, but it's a little off for me:
SELECT
,EMP
,RECORD_DATE
,LAG(DEPARTMENT, 1, 0) OVER (PARTITION BY EMP ORDER BY RECORD_DATE) PREV
FROM EMP
Here are some values to work with:
CREATE TABLE #EMP
(
EMP VARCHAR(30) NOT NULL ,
RECORD_DATE DATE NOT NULL ,
DEPARTMENT VARCHAR(30) NOT NULL
);
INSERT INTO #EMP (EMP, DATE_WORKED, CITY)
VALUES
('123456','2020-01-01','HR'),
('123456','2020-02-01','HR'),
('123456','2020-03-01','FINANCE'),
('123456','2020-04-01','FINANCE'),
('987654','2020-01-01','HR'),
('987654','2020-02-01','HR'),
('987654','2020-03-01','HR'),
('987654','2020-04-01','LEGAL')
You could do it using LAG function:
WITH data AS(
SELECT 123456 EMPID, DATE '2020-01-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 123456 EMPID, DATE '2020-02-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 123456 EMPID, DATE '2020-03-01' RECORD_DATE, 'FINANCE' DEPARTMENT FROM dual UNION ALL
SELECT 123456 EMPID, DATE '2020-04-01' RECORD_DATE, 'FINANCE' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-01-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-02-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-03-01' RECORD_DATE, 'HR' DEPARTMENT FROM dual UNION ALL
SELECT 987654 EMPID, DATE '2020-04-01' RECORD_DATE, 'LEGAL' DEPARTMENT FROM dual
)
SELECT * FROM(
SELECT
EMPID,
RECORD_DATE MOVEMENT_DATE,
LAG(DEPARTMENT) OVER (PARTITION BY EMPID ORDER BY RECORD_DATE) DEPARTMENT_BEFORE,
DEPARTMENT DEPARTMENT_AFTER
FROM data
)
WHERE DEPARTMENT_BEFORE <> DEPARTMENT_AFTER;
EMPID MOVEMENT_DATE DEPARTMENT_BEFORE DEPARTMENT_AFTER
---------- --------------- ----------------- -----------------
123456 2020-03-01 HR FINANCE
987654 2020-04-01 HR LEGAL

Need help understanding range between in SQL window functions

I am trying understand how range clause is working in below case (oracle database)
SELECT
EMPID,NAME,
HIRE_DATE_1,
SALARY,
count(1) over(order by HIRE_DATE_1 range between 1 preceding and 1 preceding) as PREV_MIN_SA
FROM (
SELECT
EMPID,
NAME,
(EXTRACT(year from HIRE_DATE)*10000)+(EXTRACT(MONTH FROM HIRE_DATE) * 100) + (extract(DAY from HIRE_DATE)) as HIRE_DATE_1,SALARY
FROM EMPLOYEE A order by HIRE_DATE,SALARY
) A
ORDER BY HIRE_DATE_1
Result Set :
EMPID NAME HIRE_DATE_1 SALARY PREV_MIN_SA
100 Ravi 20180101 5000 0
101 Kumar 20180101 7000 0
102 Satish 20180101 13000 0
103 Naresh 20180102 7500 3
105 Lalith 20180104 17300 0
104 Suresh 20180104 40000 0
106 Latha 20180201 16000 0
The inner query is just converting date into numeric YYYYMMDD format.
My intention is to get the count of people who joined immediately prior to the date of the employee in each record. I can take the count of rows with same HIRE_DATE and use LAG function but somehow not understand how the sql is returning this result set.
Also, once I am done with the counts I would like to get the MIN(SALARY) of the employees who joined immediately prior to the employee in current row and find the difference in salaries so wondering if somehow I can define the window to only have all records with immediately prior HIRE_DATE.
Thanks
This should get the preceding hire date...
SELECT
EMPID,NAME, HIRE_DATE, SALARY,
MAX(HIRE_DATE) OVER (ORDER BY HIRE_DATE
RANGE BETWEEN UNBOUNDED PRECEDING
AND INTERVAL '1 DAY' PRECEDING
)
AS PREV_HIRE_DATE
FROM
EMPLOYEE
Then I think you need to join back on to the employee table to find the number of employees and their min salary?
SELECT
*
FROM
(
SELECT
EMPID,NAME, HIRE_DATE, SALARY,
MAX(HIRE_DATE) OVER (ORDER BY HIRE_DATE
RANGE BETWEEN UNBOUNDED PRECEDING
AND INTERVAL '1 DAY' PRECEDING
)
AS PREV_HIRE_DATE
FROM
EMPLOYEE
)
EMPS
LEFT JOIN
(
SELECT
HIRE_DATE,
COUNT(*) AS COUNT_EMPS,
MIN(SALARY) AS MIN_SALARY
FROM
EMPLOYEE
GROUP BY
HIRE_DATE
)
PREV_EMPS
ON PREV_EMPS.HIRE_DATE = EMPS.PREV_HIRE_DATE
EDIT:
Maybe try something like this? (I have to run, good luck!)
WITH
ranked AS
(
SELECT
*,
DENSE_RANK() OVER (ORDER BY HIRE_DATE) AS HIRE_SEQ_ID
FROM
EMPLOYEE
)
SELECT
*,
MIN(SALARY) OVER (ORDER BY HIRE_SEQ_ID
RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING
)
AS PREV_MIN_SALARY,
COUNT(*) OVER (ORDER BY HIRE_SEQ_ID
RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING
)
AS COUNT_PREV_EMPS
FROM
ranked
This does what you're after, I believe (I've left it as an exercise for you to find the difference between the max and min salaries!):
WITH employee AS (SELECT 100 empid, 'Ravi' NAME, to_date('01/01/2018', 'dd/mm/yyyy') hire_date, 5000 salary FROM dual UNION ALL
SELECT 101 empid, 'Kumar' NAME, to_date('01/01/2018', 'dd/mm/yyyy') hire_date, 7000 salary FROM dual UNION ALL
SELECT 102 empid, 'Satish' NAME, to_date('01/01/2018', 'dd/mm/yyyy') hire_date, 13000 salary FROM dual UNION ALL
SELECT 103 empid, 'Naresh' NAME, to_date('02/01/2018', 'dd/mm/yyyy') hire_date, 7500 salary FROM dual UNION ALL
SELECT 104 empid, 'Lalith' NAME, to_date('04/01/2018', 'dd/mm/yyyy') hire_date, 17300 salary FROM dual UNION ALL
SELECT 105 empid, 'Suresh' NAME, to_date('04/01/2018', 'dd/mm/yyyy') hire_date, 40000 salary FROM dual UNION ALL
SELECT 106 empid, 'Latha' NAME, to_date('01/02/2018', 'dd/mm/yyyy') hire_date, 16000 salary FROM dual)
SELECT empid,
NAME,
hire_date,
salary,
COUNT(*) OVER (ORDER BY hire_date RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING) prev_day_hire_count,
MIN(salary) OVER (ORDER BY hire_date RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING) prev_day_min_sal,
MAX(salary) OVER (ORDER BY hire_date RANGE BETWEEN 1 PRECEDING AND 1 PRECEDING) prev_day_max_sal
FROM employee;
EMPID NAME HIRE_DATE SALARY PREV_DAY_HIRE_COUNT PREV_DAY_MIN_SAL PREV_DAY_MAX_SAL
---------- ------ ----------- ---------- ------------------- ---------------- ----------------
100 Ravi 01/01/2018 5000 0
101 Kumar 01/01/2018 7000 0
102 Satish 01/01/2018 13000 0
103 Naresh 02/01/2018 7500 3 5000 13000
104 Lalith 04/01/2018 17300 0
105 Suresh 04/01/2018 40000 0
106 Latha 01/02/2018 16000 0

Dense Rank with order by

I have Assignment Table like this
EMPLID | RCD | COMPANY | EFFDT | SALARY
---------------------------------------------------
100 | 0 | xyz | 1/1/2000 | 1000
100 | 0 | xyz | 1/15/2000 | 1100
100 | 0 | xyz | 1/31/2000 | 1200
100 | 0 | ggg | 2/15/2000 | 1500
100 | 1 | abc | 3/1/2000 | 2000
100 | 1 | abc | 4/1/2000 | 2100
I need a counter which should increase whenever RCD or Company combination changes and it should be order by effdt.
EMPLID | RCD | COMPANY | EFFDT | SALARY | COUNTER
-------|-----|---------|---------------|-------------|----------
100 | 0 | xyz | 1/1/2000 | 1000 | 1
100 | 0 | xyz | 1/15/2000 | 1100 | 1
100 | 0 | xyz | 1/31/2000 | 1200 | 1
100 | 0 | ggg | 2/15/2000 | 1500 | 2
100 | 1 | abc | 3/1/2000 | 2000 | 3
100 | 1 | abc | 4/1/2000 | 2100 | 3
I tried Dense_Rank function with order by EMPLID , RCD , COMPANY , It provides me Counter but its not in order by effdt.
SELECT EMPLID,RCD,COMPANY,EFFDT,
DENSE_RANK() over (order by EMPLID , RCD , COMPANY) AS COUNTER
FROM ASSIGNMENT ;
Order by EFFDT , Gives incremental counter 1 ... 6
SELECT EMPLID,RCD,COMPANY,EFFDT,
DENSE_RANK() over (order by EFFDT) AS COUNTER
FROM ASSIGNMENT;
Kindly help me to find out what I am missing.
Try LAG
WITH flagged AS (
SELECT *,
CASE WHEN LAG(RCD) OVER(PARTITION BY EMPLID ORDER BY EFFDT) = RCD
AND LAG(COMPANY) OVER(PARTITION BY EMPLID ORDER BY EFFDT) = COMPANY THEN 0 ELSE 1 END strtFlag
FROM tbl
)
SELECT EMPLID, RCD, COMPANY, EFFDT, SALARY, SUM(strtFlag) OVER(PARTITION BY EMPLID ORDER BY EFFDT) COUNTER
FROM flagged
alternatively, with DENSE_RANK() of group
WITH grps AS (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY EMPLID ORDER BY EFFDT) -
ROW_NUMBER() OVER(PARTITION BY EMPLID, RCD, COMPANY ORDER BY EFFDT) grp
FROM tbl
)
SELECT EMPLID, RCD, COMPANY, EFFDT, SALARY
, DENSE_RANK() OVER(PARTITION BY EMPLID ORDER BY grp) COUNTER
FROM grps
Anyway looks like two steps are needed to get dense numbering.
This should work - with the clarification that a combination of rcd and company should keep the same "counter" even if it appears in non-consecutive periods. I added to more rows to the test data to make sure I get the correct result.
Like Serg's solutions (which answer a different question), the solution does one pass over the base data, and then a second pass over the results of the first pass (all in memory, so it should be relatively fast). There's no way around that - this requires two different analytic functions where one depends on the results of the other, and nested analytic functions are not allowed. (This part of the answer addresses a comment by the OP to the Answer by Serg.)
with
test_data ( emplid, rcd, company, effdt, salary ) as (
select 100, 0, 'xyz', to_date('1/1/2000' , 'mm/dd/yyyy'), 1000 from dual union all
select 100, 0, 'xyz', to_date('1/15/2000', 'mm/dd/yyyy'), 1100 from dual union all
select 100, 0, 'xyz', to_date('1/31/2000', 'mm/dd/yyyy'), 1200 from dual union all
select 100, 0, 'ggg', to_date('2/15/2000', 'mm/dd/yyyy'), 1500 from dual union all
select 100, 1, 'abc', to_date('3/1/2000' , 'mm/dd/yyyy'), 2000 from dual union all
select 100, 1, 'abc', to_date('4/1/2000' , 'mm/dd/yyyy'), 2100 from dual union all
select 100, 0, 'xyz', to_date('5/1/2000' , 'mm/dd/yyyy'), 2200 from dual union all
select 100, 1, 'ggg', to_date('8/15/2000', 'mm/dd/yyyy'), 2300 from dual
)
-- end of test data; the actual solution (SQL query) begins below this line
select emplid, rcd, company, effdt, salary,
dense_rank() over (partition by emplid order by min_dt) as counter
from ( select emplid, rcd, company, effdt, salary,
min(effdt) over (partition by emplid, rcd, company) as min_dt
from test_data )
order by effdt -- ORDER BY is optional
;
EMPLID RCD COM EFFDT SALARY COUNTER
---------- ---------- --- ------------------- ---------- ----------
100 0 xyz 2000-01-01 00:00:00 1000 1
100 0 xyz 2000-01-15 00:00:00 1100 1
100 0 xyz 2000-01-31 00:00:00 1200 1
100 0 ggg 2000-02-15 00:00:00 1500 2
100 1 abc 2000-03-01 00:00:00 2000 3
100 1 abc 2000-04-01 00:00:00 2100 3
100 0 xyz 2000-05-01 00:00:00 2200 1
100 1 ggg 2000-08-15 00:00:00 2300 4
8 rows selected
I think you're looking for:
SELECT EMPLID,RCD,COMPANY,EFFDT,
DENSE_RANK() over (order by EMPLID , RCD , COMPANY) AS COUNTER
FROM (select * from ASSIGNMENT order by EFFDT);
or
SELECT EMPLID,RCD,COMPANY,EFFDT,
DENSE_RANK() over (order by EMPLID , RCD , COMPANY) AS COUNTER
FROM (select * from ASSIGNMENT order by EMPLID , RCD , COMPANY, EFFDT);