I have a table like this, and I want to return the top two person (by name) with the highest salary, and also the record with the corresponding salary. Here is the table
id, name, salary
1, Tom, 200
2, Tom, 300
3, Bob, 400
4, Bob, 500
5, Alice, 600
6, Alice, 700
I used this command
Employer.select("employers.*, max(employers.salary) as maxsalary").group("employers.name").order("maxsalary desc").limit(2)
Desired return:
id, name, salary
6, Alice, 700
4, Bob, 500
What I got seems to be like this:
id, name, salary
5, Alice, 600
3, Bob, 400
Is there anyway to select the records responding to the max ? Any comment/answer is much appreciated.
This Question is very tricky actually!
It seems very easy but it's not.
The Query:
Employer.joins(%Q|
LEFT JOIN employers as e
ON
e.name = employers.name
AND
employers.salary < e.salary
|).where('e.salary IS NULL').order('employers.salary DESC').limit(2)
HOW DOES THAT WORK! (I've been there)
We want to make sure that we only have the highest salary for each employer and then get the highest 2 of those.
Some Theoretical Stuff (skip this part if you only want to understand the query)
Let Salary be a function S(name,id) where it returns a value given the name and id
To prove that the given salary (S(name,id)) is the highest we have to prove that
We want to prove either
∀x S(name,id) > S(name,x) (this salary is higher than all other
salaries for that name)
OR
¬∃x S(name, id) < S(name, x) (there exists no higher salary for
that name)
The first approach will need us to get all the records for that name which I do not really like.
The second one will need a smart way to say there can be no record higher than this one.
Back to SQL
If we left joins the table on the name and salary being less than the joined table:
%Q|
LEFT JOIN employers as e
ON
e.name = employers.name
AND
employers.salary < e.salary
|
we make sure that all records that has another record with higher salary for the same user to be joined:
employers.id, employers.name, employers.salary, e.id, e.name, e.salary
1 , Tom , 200 , 2 , Tom , 300
2 , Tom , 300
3 , Bob , 400 , 4 , Bob , 500
4 , Bob , 500
5 , Alice , 600 , 6 , Alice , 700
6 , Alice , 700
That will help us filter for the highest salary for each employer with no grouping needed:
where('e.salary IS NULL')
employers.id, employers.name, employers.salary, e.id, e.name, e.salary
2 , Tom , 300
4 , Bob , 500
6 , Alice , 700
Now all we need to do is sort:
order('employers.salary DESC')
employers.id, employers.name, employers.salary, e.id, e.name, e.salary
6 , Alice , 700
4 , Bob , 500
2 , Tom , 300
Then limit
limit(2)
employers.id, employers.name, employers.salary, e.id, e.name, e.salary
6 , Alice , 700
4 , Bob , 500
And that's the answer we need.
Why don't we
1.
Employer.order('salary desc').limit(2)
Because this will get us the records with the highest salaries independent of the name
employers.id, employers.name, employers.salary
5 , Alice , 600
6 , Alice , 700
2.
Employer.select('DISTINCT(name)').order('salary desc').limit(2)
The problem with that is it will only keep the first appearance of the name then sort
employers.id, employers.name, employers.salary
1 , Tom , 200
3 , Bob , 400
5 , Alice , 600
Related
emp_id
emp
level
manager_id
manager
100
sam
1
100
sam
200
jack
2
100
sam
300
jill
2
100
sam
400
claire
3
200
jack
500
reed
3
300
jill
600
derrick
4
400
claire
700
bill
4
500
reed
I have a table with employees and their respective managers in column 'emp' and 'manager' respectively. The numbers in the 'level' column represent different levels within an organisation for the employees in column 'emp'.
How do i write a query to get the manager name from the previous level as an entry in step and so forth.
emp_id
emp
level
manager_id
manager
l1
l2
l3
l4
100
sam
1
100
sam
NULL
NULL
NULL
NULL
200
jack
2
100
sam
sam
NULL
NULL
NULL
300
jill
2
100
sam
sam
NULL
NULL
NULL
400
claire
3
200
jack
sam
jack
NULL
NULL
500
reed
3
300
jill
sam
jill
NULL
NULL
600
derrick
4
400
claire
sam
jack
claire
NULL
700
bill
4
500
reed
sam
jill
reed
NULL
You seem to be looking for a recursive query. One that keeps joining on the next level until there are no more levels to join to.
That can be used to get all of an employees managers, each manager as a new row.
You then want to pivot those rows in to columns. Note, however, that SQL is statically and strongly typed, which means that if you want a pivoted view, you have to choose in advance how many columns you're going to have.
For example...
WITH
recurse_upwards AS
(
SELECT
emp.emp_id,
emp.emp AS emp_name,
0 AS level,
mgr.emp_id AS manager_id,
mgr.emp AS manager_name,
mgr.manager_id AS next_manager_id
FROM
example AS emp
LEFT JOIN
example AS mgr
ON mgr.emp_id = emp.manager_id
UNION ALL
SELECT
emp.emp_id,
emp.emp_name,
emp.level + 1,
mgr.emp_id,
mgr.emp,
mgr.manager_id
FROM
recurse_upwards AS emp
INNER JOIN
example AS mgr
ON mgr.emp_id = emp.next_manager_id
)
SELECT
emp_id,
emp_name,
MAX(CASE WHEN level = 0 THEN manager_id END) AS manager_id,
MAX(CASE WHEN level = 0 THEN manager_name END) AS manager_name,
MAX(CASE WHEN level = 1 THEN manager_name END) AS manager_1_name,
MAX(CASE WHEN level = 2 THEN manager_name END) AS manager_2_name,
MAX(CASE WHEN level = 3 THEN manager_name END) AS manager_3_name,
MAX(CASE WHEN level = 4 THEN manager_name END) AS manager_4_name
FROM
recurse_upwards
GROUP BY
emp_id,
emp_name
ORDER BY
emp_id
Demo : https://dbfiddle.uk/Tj7rZ5bT
Consider following dataset:
id
name
mgr_id
salary
bonus
1
Paul
1
68000
10000
2
Lucas
2
29000
null
3
Max
1
50000
20000
4
Zack
2
30000
null
I now want to find the manager who pays his subordinates the highest average salary plus bonus. A manager is someone who is present in of the mgr_id cells. So in this example Paul and Lucas are managers because their id is present in the mgr_id column of themselves and Max for Paul and Zack for Lucas.
Basically I want MAX(AVG(salary + bonus)) and then grouped by the mgr_id. How can I do that?
I am using SQLite.
My expected output in this example would be simply the employee name 'Paul' because he has 2 subordinates (himself and Max) and pays them more than the other manager Lucas.
SELECT
mrg_id
, pay_avg
FROM
(
SELECT mrg_id
, AVG(salary + COALESCE(bonus,0)) pay_avg
FROM <table>
GROUP
BY mrg_id
) q
ORDER
BY pay_avg
desc
LIMIT 1
select top 1 t1.mgr_id,AVG((t1.salary)+(t1.bonus)) as tot_sal
from #tbl_emps as t1
group by t1.mgr_id
order by AVG((t1.salary)+(t1.bonus)) desc
I have a table Called EmployeeLevel
Id Position ShouldApproveLeave
1000 1 0
2000 2 1
3000 3 0
4000 4 0
Note that the bool column ShouldApproveLeave is true for only one row
I have a table Employee with the following structure with LevelId as foreign key to EmployeeLevel
Id ParentId Name LevelId
1000 NULL Jack 1000
2000 1000 John 2000
3000 2000 Nick 3000
4000 3000 James 4000
I need a query to get employee details, position details as well as the manager responsible for his leave approval as below
query (id = 4000)
Id Name LevelPosition LeaveApprovalLevel LeaveApprovingManager
4000 James 4 2 John
query (id = 3000)
Id Name LevelPosition LeaveApprovalLevel LeaveApprovingManager
3000 Nick 3 2 John
query (id = 2000)
Id Name LevelPosition LeaveApprovalLevel LeaveApprovingManager
2000 John 2 2 John
Also I want to check leave approving manager only upwards in the hierarchy.
query (id = 1000)
Id Name LevelPosition LeaveApprovalLevel LeaveApprovingManager
1000 Jack 1 2 NULL
I wrote a CTE to get the manager with LeaveApproving bit set. Getting the employee with level is also straightforward. My issue is to get all these details in the same row.
So the question really is I got individual results for employee details and Leave Approving manager. How can I get this in a single row?
I managed to get individual queries for the following
employee details
Id ParentId Name Position
4000 3000 James 4
LeaveApprovalLevel details
Level
2
LeaveApprovingManager details
Id ParentId Name
2000 1000 John
How can I join these so that I will get this?
Id Name LevelPosition LeaveApprovalLevel LeaveApprovingManager
4000 James 4 2 John
Any tips are appreciated.
Here is one approach:
Get the hierarchy for the employee.
Use joins to get the information on the flag you want.
Use joins to bring back all the information you want.
As a query:
with cte as (
select e.id, e.parentid, 1 as lev
from employee e
where id = 4000
union all
select cte.id, e.parentid, lev + 1
from cte join
employee e
on e.id = cte.parentid
)
select e.*, el.*
from cte join
employee e
on cte.id = e.id join
employee el
on cte.parentid = el.id join
EmployeeLevel eml
on cte.parentid = eml.id;
try this
SELECT e1.Id, e1.Name, el1.Position as LevelPosition, el2.Position as LeaveApprovalLevel, e2.Name as LeaveApprovingManager FROM Employee e1 INNER JOIN EmployeeLevel el1 ON e1.LevelId = el1.Id LEFT JOIN Employee e2 ON e1.ParentId = e2.Id LEFT JOIN EmployeeLevel el2 ON e2.LevelId = el2.Id;
Here is the response
Check, here is the working Fiddle
I have two tables: employee and dependent
Structure
- Table employee
Name Null? Type
EMPLOYEEID NOT NULL NUMBER(3)
LNAME NOT NULL VARCHAR2(15)
FNAME NOT NULL VARCHAR2(15)
POSITIONID NUMBER(1)
SUPERVISOR NUMBER(3)
HIREDATE DATE
SALARY NUMBER(6)
COMMISSION NUMBER(5)
DEPTID NUMBER(2)
QUALID NUMBER(1)
- Table Dependent
Name Null? Type
EMPLOYEEID NOT NULL NUMBER(3)
DEPENDENTID NOT NULL NUMBER(1)
DEPDOB NOT NULL DATE
RELATION NOT NULL VARCHAR2(8)
Data
- Table employee
EMPLOYEEID LNAME FNAME POSITIONID SUPERVISOR HIREDATE SALARY COMMISSION DEPTID QUALID
111 Smith John 1 15/04/60 265000 35000 10 1
246 Houston Larry 2 111 19/05/67 150000 10000 40 2
123 Roberts Sandi 2 111 02/12/91 75000 10 2
543 Dev Derek 2 111 15/03/95 80000 20000 20 1
433 McCall Alex 3 543 10/05/97 66500 20 4
135 Garner Stanley 2 111 29/02/96 45000 5000 30 5
200 Shaw Jinku 5 135 03/01/00 24500 3000 30
222 Chen Sunny 4 123 15/08/99 35000 10 3
- Table Dependent
EMPLOYEEID DEPENDENTID DEPDOB RELATION
543 1 28/09/58 Spouse
543 2 14/10/88 Son
200 1 10/06/76 Spouse
222 1 04/02/75 Spouse
222 2 23/08/97 Son
222 3 10/07/99 Daughter
111 1 12/12/45 Spouse
And I have two employees: One employee has one child, and the other has two children. I got it with the following query:
Query
SELECT employee.employeid, lname, fname, salary, dependent.relation
FROM employee INNER JOIN dependent
ON employee.employeeid = dependent.employeeid
WHERE dependent.relation = 'Daughter' OR dependent.relation = 'Son';
Result
EMPLOYEEID LNAME FNAME SALARY RELATION
543 Dev Derek 80000 Son
222 Chen Sunny 35000 Son
222 Chen Sunny 35000 Daughter
And my homework is increase the salary $ 100 per child, and I tried with the following code:
UPDATE employee
SET SALARY = salary + 100
WHERE employee.employeeid IN (
SELECT employee.EMPLOYEEID FROM employee INNER JOIN dependent
ON employee.employeeid = dependent.employeeid
WHERE dependent.relation = 'Daughter' OR dependent.relation = 'Son' );
But, i don´t get the expect result. The employee "Chen" has two children, but his salary only increase once, no twice. Her final salary is $ 35100, no $ 35200.
Can anybody help me?
I can't test on Oracle version 10, but this should work fine:
Well, apparently, it doesn't work in Oracle 10 (though it does work correctly in Oracle 12). I added an alternative at the bottom of the post using merge that should work correctly.
update (
select e.salary,
d.childcnt * 100 as increase
from employee e
join (select d.employeeid, count(*) as childcnt
from dependent d
where d.relation in ('Son', 'Daughter')
group by d.employeeid) d
on d.employeeid = e.employeeid
) set salary = salary + increase
Here is another way of achieving the same thing using the merge command. This should work correctly in Oracle 10:
merge into employee dest
using (
select employeeid,
count(*) * 100 as increase
from dependent d
where relation in ('Son', 'Daughter')
group by employeeid
) src
on (src.employeeid = dest.employeeid)
when matched then
update set dest.salary = dest.salary + src.increase
I have the table below.Using salary as condition I want to get multiple rows.
Below is current table call it employee.
empid name salary
-----------------------------------
1 A1 alex 20000
2 B2 ben 4500
3 C1 carl 14000
compare the salary to certain fixed values, and every time the salary is larger than the fixed value, show a record in output.My attempt condition case is close to this:
incometype= case When salary<6000 then 101 When salary Between 6000 And 18000 Then
102 Else 103 End
Desired ouput would be:
empid name salary incometype
------------------------------------------
1 A1 alex 20000 101
2 A1 alex 20000 102
3 A! alex 20000 103
4 B2 ben 4500 101
5 C1 carl 14000 101
6 C1 carl 14000 102
I have tried using union but union will give me 3 rows for each record even when value meets 1st condition.
Your question is unclear, because your logic implies that you should only have 3 output rows for 3 input rows. Your output however implies that you want to compare the salary to certain fixed values, and every time the salary is larger than the fixed value, show a record in output.
If the former is the case, Minh's query is all you need. In the latter case, you can do something like this:
select e.*, m.incometype
from employee e
left join
(
select 0 as threshold, 101 as incometype
union
select 5999 as threshold, 102 as incometype
union
select 17999 as threshold, 103 as incometype
) m
on e.salary > m.threshold
order by e.empid
If you want to add a calculate column i.e. one with values calculated using columns in this query, you can simply add it as a column in the select clause, like so:
select e.*,
m.incometype,
case
when <first condition> then <business logic here>
....
else <handle default case>
end as yourcomputedcolumn
from
...
This returns 3 rows and enough for your need:
SELECT empid, name, salary,
case When salary<6000 then 101
When salary Between 6000 And 18000 Then 102
Else 103 End as incometype
FROM employee;
Not very clear on the requirement, however the following worked for me:
Select
EmpId,Name,Sal,101 IncomeType
from Emp
Union all
Select
EmpId,Name,Sal,102
from Emp
Where Sal > 6000
union all
Select
EmpId,Name,Sal,103
from Emp
Where Sal > 18000;