SQL query to find out third highest salary involving two tables - sql-server-2008

I have two tables:
Persons:
empid(primary key)
firstname
lastname
email
Details:
Did(primary key)
salary
designation
empid
Now I want to select firstname, lastname, salary of the employee with the 3rd highest salary.
KEEP IT SIMPLE SINCE I AM A BEGINNER.
Also it would be great if you can suggest me a site where I can find SQL query exercises with solutions (I do not need an online interpreter, I have SQL Server 2008 to practice on ), I have completed w3schools (which was awesome) but I need exercises now, I tried sql.ex.ru but it was not that helpful.

try this:
SELECT top 1 P.*,a.salary
FROM Persons p
JOIN
(select top 3 empid,salary from Details order by salary desc)a
ON p.empid=a.empid
ORDER BY p.salary
or
;WITH CTE AS
(SELECT *,
ROW_NUMBER() OVER (ORDER BY SALARY DESC) AS ROW_NUM
FROM DETAILS)
SELECT *
FROM PERSONS P
JOIN CTE C
ON P.EMPID=C.EMPID
where c.ROW_NUM=3

Here is a little trick. What it does is add a sequence number or row number onto each row, giving the ordering by salary. It then just chooses the third one:
select FirstName, LastName, Salary
from (select p.FirstName, p.LastName, d.salary,
row_number() over (order by salary desc) as seqnum
from persons p join
details d
on p.empid = d.empid
) t
where seqnum = 3
The row_number() function does this calculation. It is a very useful function, that you can add to your SQL knowledge.

SELECT TOP 1 persons.FirstName, persons.LastName, Salary
FROM persons inner join Details
ON persons.empid=Details.empid
where Salary not in(SELECT TOP 2 Salary from Details ORDER BY Salary DESC)
order by Salary desc

Related

Reduce number of subqueries

I've 2 tables emp and expenditure.
Emp:
ID, NAME
Expenditure:
ID, EMP_ID, AMOUNT
Each emp has a limit of 100 that he/she can spend. We want to check which emp has expenditure > 100.
Output attributes needed: Emp name, exp id, amount
My query:
SELECT E.NAME,
EXP.ID,
EXP.AMOUNT
FROM EMP E
INNER JOIN expenditure EXP ON E.ID = EXP.EMP_ID
WHERE E.ID in
(SELECT EMP_ID
FROM
(SELECT EMP_ID,
SUM(AMOUNT) AS TOTAL
FROM expenditure
GROUP BY EMP_ID
HAVING SUM(AMOUNT) > 100.00
ORDER BY TOTAL DESC) SUBQ)
ORDER BY EXP.AMOUNT desc;
Is it possible to optimize this?
Just like code, SQL queries can be written in many different ways. Run an Execution Plan on your SQL. Check here and here
Below is more "conciser" version although it may not be any more optimised than your current code. Use Execution Plans to analyse performance.
SELECT E.NAME,
E.ID, -- THIS IS EMPLOYEE ID NOT EXPENDITURE ID
EXP.EMP_SPENT
FROM EMP E
JOIN (SELECT EMP_ID, sum(AMOUNT) as EMP_SPENT FROM expenditure GROUP BY EMP_ID) EXP
ON E.ID = EXP.EMP_ID
WHERE EXP.EMP_SPENT > 100;
Additionally...
I noticed that question is a bit confusing. The original query spits out "every line" of expense of an employee, whose "total" expenses are above 100. The text however, says "Which employee has gone above 100". These are two different questions. My answer above is for the latter "Who has gone above". It will NOT list all expenses of employees who had spent over 100, but only a list of employees who have gone above 100 and their total expenditure.
You can use a simple aggregation with HAVING clause such as
SELECT e.name, e.id, SUM(amount) AS total
FROM emp e
JOIN expenditure ep
ON e.id = ep.emp_id
GROUP BY e.name, e.id
HAVING SUM(amount) > 100
but it's not logical to have a non-aggregated column along with aggregated ones within the result

How to select only ranks which occur once except the first rank?

I have 2 tables
Table1: student
Table2: problem
Now we need to find out which student has solved how many problems and in the output we need to display student_id, student_name and no_of_prob in descending order of no_of_prob.
If more than one student has equal number of problems then 1. If the no_of_prob is highest among all others then keep all of them. 2. If the no_of_prob is not the highest, then do not keep any of these.
For example, when we group by student_id, we get this
As student_id 2 and 5 have equal no_of_prob but as their no_of_prob is highest, we will keep both student_id 2 and 5. But the student_id 1 and 3 have equal no_of_prob but its not the highest therefore we need to drop both of these. The final table should be like this.
Now i have found out how to get till the table number 3 above but I don't know how am I supposed to keep the 1st rankers but remove lower rankers if repeated. This is my code below
select s.student_id,student_name, count(problem_id) as no_of_prob,
rank() over(order by count(problem_id) desc) as st_rank
from student s inner join problem p on s.student_id = p.student_id
group by s.student_id;
You can use the RANK() function to get the 1st rankers along with rows having count as 1 with the subquery.
SELECT student_id,
student_name,
no_of_prob
FROM (SELECT p.student_id,student_name,
COUNT(*) AS no_of_prob,
RANK() OVER(ORDER BY COUNT(*) DESC) rk
FROM problem p
JOIN student s ON p.student_id = s.student_id
GROUP BY p.student_id,student_name
ORDER BY COUNT(*) DESC) a
WHERE rk = 1 OR no_of_prob = 1
Check Demo Here
Output
Try this query. Hopefully it will be work fine.
SELECT p.student_id,s.student_name,count(*) as no_of_prb
FROM `problem` p
left join student s on p.student_id=s.student_id
group by p.`student_id`
having
no_of_prb not in (select count(*) as total from problem group by student_id having total>1)
OR
no_of_prb = (select count(*) as total from problem group by student_id order by total desc limit 1)
order by no_of_prb desc

Simple SQL query returning null value

I just started studying SQL queries.
I am practicing on this site: https://www.techonthenet.com/sql/joins_try_sql.php
I want to find:
"the name of the employee with the highest salary for every department".
My query is:
SELECT first_name, max(salary) FROM employees, departments
WHERE departments.dept_id=employees.dept_id
GROUP BY employees.dept_id
And I get null value for first_name :
I understand that the problem is due to the group by expressions. But how can I solve this?
Tables:
You can alternatively use row_number() like below, you don't need to join to departments table unless you need to show the name of the department:
Select e.*
from employees e
INNER JOIN
(
SELECT e.id, e.dept_id. e.first_name,
rn=row_number() over (partition by e.dept_id order by e.salary desc)
FROM employees e
) x ON x.id = e.id
where x.rn = 1
EDIT
(Since OP does not want to use row_number() function amd it turned out the query will be used in mysql instead of sql server) -> Can you please try this:
select em.*
from employees em, (
Select dept_id, max(salary) salary
from employees e
group by dept_id
) x on x.dept_id=em.dept_id and x.salary = em.salary
That should work but the online compiler does not accept join with a sub-query as far as I understand. Easiest solution I can think of, in this case:
select em.*
from employees em
where salary = (select max(salary) from employees em2 where em.dept_id = em2.dept_id)
SELECT top 2 first_name,max(salary) FROM employees, departments
WHERE departments.dept_id=employees.dept_id
GROUP BY first_name
order by max(salary) desc
try this:
SELECT e.first_name, e.salary FROM employees as e
INNER JOIN departments as d ON d.dept_id=e.dept_id
WHERE e.salary IN (SELECT max(salary) FROM employees GROUP BY dept_id)
Try this:
select first_name, b.dept_id, salary from employees a,
(
SELECT employees.dept_id, max(salary) as salary FROM employees, departments
WHERE departments.dept_id=employees.dept_id
GROUP BY employees.dept_id
)b where a.salary = b.salary and a.dept_id= b.dept_id

Creating a ranking column from a queried column in MySQL

I want to build a ranking column into my query--I've found some similar cases on Stack but this one's a little different and I can't quite make it work. I have a single table, EnrollmentX, with two columns, a unique StudentID and a GroupId (for sake of argument, groups 1:3). I need to simultaneously count the number of students in each of these three groups and then rank the the groups by number of students. I've made it as far as the counting:
SELECT
EnrollmentX.GroupId,
COUNT(EnrollmentX.StudentId) AS StudentCnt
FROM EnrollmentX
GROUP BY
EnrollmentX.GroupId
This puts out two columns, one for GroupId, 1:3, and one for StudentCnt, with the correct number of students in each group. What I can't work out is how to use that StudentCnt column after building it to create a third ranking column.
IF you are on mysql 8 there are more readable options.Change the order in the inner query if you want a different rank.
SELECT GroupId, StudentCnt, #Rank:=#Rank + 1 AS rank FROM
(SELECT EnrollmentX.GroupId,
COUNT(EnrollmentX.StudentId) AS StudentCnt
FROM EnrollmentX
GROUP BY
EnrollmentX.GroupId
ORDER BY StudentCnt DESC
) x CROSS JOIN (SELECT #Rank:=0) y
Try this query:
select ex.GroupId, ex.StudentId, exg.cnt from EnrollmentX ex
left join (
SELECT GroupId, COUNT(*) cnt
FROM EnrollmentX
GROUP BY GroupId
) exg on ex.GroupId = exg.GroupId
order by exg.cnt
try it..
SET #Rank = 0;
SELECT #Rank:=#Rank + 1 rank, EnrollmentX.GroupId,
COUNT(EnrollmentX.StudentId) StudentCnt
FROM EnrollmentX
GROUP BY
EnrollmentX.GroupId
ORDER BY StudentCnt DESC;

SQL: return multiple rows with maximal value on a column

You can find the problem from here.
When there are multiple people with the highest salary in each department, the following SQL command output the expected results - all employees with highest salary are output:
select d.Name as Department, e.Name as Employee, e.Salary as Salary
from
(select Name, Salary, DepartmentId
from Employee
having (Salary, DepartmentId) in (
select max(Salary), DepartmentId
from Employee
group by DepartmentId)
)e, Department d
where e.DepartmentId = d.Id
However, the following command only output one employee even if there are multiple employees with the same highest salary:
select d.Name as Department, e.Name as Employee, e.Salary as Salary
from
(select Name, Salary, DepartmentId
from Employee
group by DepartmentId
having Salary = max(Salary)
)e, Department d
where e.DepartmentId = d.Id
Can anyone explain to me why the latter one does not work? Thanks!
The reason the second query doesn't work is because it's getting the max salary regardless of the department.
The way I would have done this, despite the valid first query (which is not using the standard JOIN syntax like it should), would be to first get the highest salary for each department like this:
SELECT departmentID, MAX(salary)
FROM employee
GROUP BY departmentID;
Once you have that, you can use a self join to only select the rows with that salary and department. This will include multiple people if they have the same salary:
SELECT e.*
FROM employee e
JOIN(
SELECT departmentID, MAX(salary) AS maxSalary
FROM employee
GROUP BY departmentID) tmp ON tmp.departmentID = e.departmentID AND tmp.maxSalary = e.salary
For further explanation, consider just the inner subquery you have:
SELECT name, department, salary
FROM employee
GROUP BY department
HAVING salary = MAX(salary);
The first problem with this is that you are selecting rows that are not consistent within your group. For each department id, you have multiple name values and multiple salary values, which will be chosen arbitrarily when you do the grouping. In my example, and the first, you can see that the grouping explicitly pulls the (departmentID, salary) pair for the max salary in each department, which the second one does not.
Here is an SQL Fiddle example to visually see the differences between your second query and mine.