Max function returning multiple values [SQL] - sql

I have 3 tables: money, student, faculty. This query returns each faculty and highest stipend in each one of them.
select
f.name as "FACULTY_NAME",
max(stipend) as "MAX_STIPEND"
from
money m, student s
inner join
faculty f on f.id_faculty = s.faculty_id
where
m.student_id = s.id_student
group by
f.id_faculty, f.name;
Query works fine:
FACULTY_NAME | MAX_STIPEND
-----------------+---------------
IT Faculty | 50
Architecture | 60
Journalism | 40
However when I add s.name to original query to also show the name of the student who received max_stipend, query is not working like it used to - it returns all of the students
select
f.name as "FACULTY_NAME",s.name,
max(stipend) as "MAX_STIPEND"
from
money m, student s
inner join
faculty f on f.id_faculty = s.faculty_id
where
m.student_id = s.id_student
group by
f.id_faculty, f.name, s.name;
Query result:
FACULTY_NAME | s.name | MAX_STIPEND
----------------+-----------+---------------
IT Faculty | Joe | 50
IT Faculty | Lisa | 10
Architecture | Bob | 60
Journalism | Fred | 5
Architecture | Susan | 5
Journalism | Tom | 40
It does the same thing using right, left and inner joins. Can someone tell where the problem is?

First, you should be using proper JOIN syntax for all your joins.
Second, you can use Oracle's keep syntax:
select f.name as FACULTY_NAME,
max(stipend) as MAX_STIPEND,
max(s.name) keep (dense_rank first order by stipend desc)
from money m join
student s
on m.student_id = s.id_student join
faculty f
on f.id_faculty = s.faculty_id
group by f.id_faculty, f.name;

However when I add s.name to original query to also show the name of the student who received max_stipend, query is not working like it used to - it returns all of the students
When you add s.name you are looking for min value for each user.
If you want the name of user who has the MAX_STIPEND you should to move to window functions. For example Dense Rank in MS SQL Server.
with cte as
(select
f.name as "FACULTY_NAME",
s.name as "STUDENT_NAME",
stipend as "MAX_STIPEND",
DENSE_RANK() OVER
(PARTITION BY f.name, s.name ORDER BY i.stipend DESC) AS Rank
from
money m
inner join student s on m.student_id = s.id_student
inner join
faculty f on f.id_faculty = s.faculty_id
)
select "FACULTY_NAME", "STUDENT_NAME"
from cte
where rank = 1
Not all sql brands have windowed functions. Here the link for dense_rank on MySQL and also dense_Rank for Oracle

Related

SQL inner joins on three tables

I have three tables that needs to be checked in order to find out on which courses professor is active.
table_teacher
table_course; and
table_teacher_holds_course
table_teacher looks like this:
username | title
---------+----------
john | professor
mark | assistant
table_course looks like this:
course_code | course_name | semester | school_year
-------------+-------------+----------+------------
course_code1| course1 |semester1 | 2015
course_code2| course2 |semester2 | 2015
course_code3| course3 |semester3 | 2015
table_teacher_holds_course looks like this:
username | course_code
---------+-------------
john |course_code1
mark |course_code2
and when I have professors username when he logs on the page, I would like to do left inner join on these three tables in order to show professors courses from table_course
Can someone help me with this, because it is first time to me to use sql to join search in several tables.
Join on the tables common fields
SELECT *
FROM table_teacher t
INNER JOIN table_teacher_holds_course hc ON t.username = hc.username
INNER JOIN table_course c ON hc.course_code = c.course_code
Relevant example:
SELECT t.title, t.username, c.course_code, c.course_name, c.semester, c.school_year
FROM table_teacher t
INNER JOIN table_teacher_holds_course hc ON t.username = hc.username
INNER JOIN table_course c ON hc.course_code = c.course_code
WHERE t.username = 'John'
Result:
title username course_code course_name semester school_year
professor John course_code1 course1 semester1 2015

Select records that appear more than once

I am trying to select records that appear more than once and are part of a specific department plus other departments.
So far the query that I have is this:
SELECT employeeCode, employeeName
FROM
Employees
WHERE
Department <> 'Technology'
AND employeeCode IN (SELECT employeeCode
FROM Employees
GROUP BY employeeCode HAVING COUNT(*) > 1)
The problem is that I want to select employees which are part of the Technology department, but they also participate in other departments.
So, they must be from the Technology department, but they could also be from the Household department. In the database it could look like:
1 | A1 | Alex | Technology
2 | A2 | Thor | Household
3 | A3 | John | Cars
4 | A3 | John | Technology
5 | A4 | Kim | Technology
6 | A4 | Kim | Video Games
So basically the query should return:
A3 | John |
A4 | Kim |
I think it's a small part that I am missing but..
Any ideas on how to filter/sort it so that it always uses the technology and the other departments?
Btw, I tried searching but I couldn't find a problem like mine..
If you want employees that could be in the technology department and another department:
select e.employeeCode, e.employeeName
from employees e
group by e.employeeCode, e.employeeName
having sum(case when e.department = 'Technology' then 1 else 0 end) > 0 and
count(*) > 1;
This assumes no duplicates in the table. If it can have duplicates, then use count(distinct department) > 1 rather than count(*) > 1.
Try this:
SELECT E.employeeCode, E.employeeName
FROM Employees E
INNER JOIN (SELECT DISTINCT E1.employeeCode, E1.employeeName
FROM Employees E
WHERE E.Department = 'Technology'
) AS A ON E.employeeCode = A.employeeCode AND E.employeeName = A.employeeName
GROUP BY E.employeeCode, E.employeeName
HAVING COUNT(*) > 1;
You can use EXISTS with correlated sub-query joining on the same table with different condition.
SELECT e1.employeeCode, e1.employeeName
FROM Employees e1
WHERE e1.Department = 'Technology'
AND EXISTS (SELECT * FROM Employees e2
WHERE e1.employeeCode = e2.employeeCode
AND e2.Department <> 'Technology')
This will work for your case:
SELECT a.employeeCode, a.employeeName
FROM Employees a, Employees b
WHERE
a.Department = 'Technology'
AND
b.Department <> 'Technology'
AND
a.employeeCode = b.employeeCode
AND
a.employeeID <> b.employeeID

SQL Query - Count() and Inner Join

I want to use an inner join to list the student ID, name, and total number of timetabled hours the student has per week, when the student has more than 4 hours per week.
I have three tables required here, student, studentReg and roomBooking as follows
student
id | fname | surname | courseCode
studentReg
sID | modCode
roomBooking
bookingID | roomCode | moduleCode | dayReq | timeReq | semester | classSize
The SQL query I have so far is
SELECT COUNT(moduleCode) AS [Lecture Hours],
id, fname, surname
FROM (student INNER JOIN studentReg ON student.id = studentReg.sID
INNER JOIN roomBooking ON studentReg.modCode = roomBooking.moduleCode)
HAVING COUNT (moduleCode) > 4;
and when I try to run this, I get "syntax error in expression"
Can anyone help me as to what the problem is?
Never sure with nested join in ms access, but I would try something like that
SELECT COUNT(moduleCode) AS [Lecture Hours],
id, fname, surname
FROM student
INNER JOIN (studentReg
INNER JOIN roomBooking ON studentReg.modCode = roomBooking.moduleCode)
ON student.id = studentReg.sID
GROUP BY id, fname, surname
HAVING COUNT (moduleCode) > 4
or maybe
SELECT COUNT(moduleCode) AS [Lecture Hours],
id, fname, surname
FROM (student INNER JOIN studentReg ON student.id = studentReg.sID)
INNER JOIN roomBooking ON studentReg.modCode = roomBooking.moduleCode
GROUP BY id, fname, surname
HAVING COUNT (moduleCode) > 4;

How to rank users and get a subset from this rank with my user and the above and below user by rank position

I am working on a query right now to get a ranking of my users. I have two tables one for users and the other one for profits where I save the amount and the user id to which is related. By getting the total of profits generated by a user I need to build a rank with three users, the user in the next higher ranked position to my user, my user and the user in the next lower ranked position to my user. For example:
id | name | total_profit | rank
-------+-----------------------------+--------------+------
10312 | John Doe | 7000.0 | 1
10329 | Michael Jordan | 5000.0 | 2
10333 | Kobe Bryant | 4000.0 | 3
10327 | Mike Bibby | 4000.0 | 3
10331 | Phil Jackson | 1000.0 | 4
In this if my user is Kobe Bryant I would need to get a rank with Michael Jordan, Kobe Bryant and Phil Jackson.
If my user is Mike Bibby I would need to get a rank with Michale Jordan, Mike Bybby and Phil Jackson.
Until now I have a query that returns me a full rank with all the users but I do not now what is a good way to get the three users that I want. I have tried to do this with ruby but I think it would be better if I do all this processing in the DB.
SELECT users.id, users.name, total_profit, rank() OVER(ORDER BY total_profit DESC)
FROM users
INNER JOIN (SELECT sum(profits.amount) AS total_profit, investor_id
FROM profits GROUP BY profits.investor_id) profits ON profits.investor_id = users.id
ORDER BY total_profit DESC;
I am using PostgresSQL 9.1.4
with s as (
select
users.id, users.name, total_profit,
rank() over(order by total_profit desc) as r
from
users
inner join
(
select sum(profits.amount) as total_profit,
investor_id
from profits
group by profits.investor_id
) profits on profits.investor_id = users.id
), u as (
select r from s where name = 'Kobe Bryant'
)
select distinct on (r) id, name, total_profit, r
from s
where
name = 'Kobe Bryant'
or r in (
(select r from u) - 1, (select r from u) + 1
)
order by r;
with cte_profits as (
select
sum(p.amount) as total_profit, p.investor_id
from profits as p
group by p.investor_id
), cte_users_profits as (
select
u.id, u.name, p.toral_profit,
dense_rank() over(order by p.total_profit desc) as rnk,
row_number() over(partition by up.total_profit order by up.id) as rn
from users as u
inner join cte_profits as p on p.investor_id = u.id
)
select c2.*
from cte as c
left outer join cte as c2 on
c2.id = c.id or
c2.rnk = c.rnk + 1 and c2.rn = 1 or
c2.rnk = c.rnk - 1 and c2.rn = 1
where c.name = 'Kobe Bryant'
order by c2.rnk
sql fiddle demo

Get top results for each group (in Oracle)

How would I be able to get N results for several groups in
an oracle query.
For example, given the following table:
|--------+------------+------------|
| emp_id | name | occupation |
|--------+------------+------------|
| 1 | John Smith | Accountant |
| 2 | Jane Doe | Engineer |
| 3 | Jack Black | Funnyman |
|--------+------------+------------|
There are many more rows with more occupations. I would like to get
three employees (lets say) from each occupation.
Is there a way to do this without using a subquery?
I don't have an oracle instance handy right now so I have not tested this:
select *
from (select emp_id, name, occupation,
rank() over ( partition by occupation order by emp_id) rank
from employee)
where rank <= 3
Here is a link on how rank works: http://www.psoug.org/reference/rank.html
This produces what you want, and it uses no vendor-specific SQL features like TOP N or RANK().
SELECT MAX(e.name) AS name, MAX(e.occupation) AS occupation
FROM emp e
LEFT OUTER JOIN emp e2
ON (e.occupation = e2.occupation AND e.emp_id <= e2.emp_id)
GROUP BY e.emp_id
HAVING COUNT(*) <= 3
ORDER BY occupation;
In this example it gives the three employees with the lowest emp_id values per occupation. You can change the attribute used in the inequality comparison, to make it give the top employees by name, or whatever.
Add RowNum to rank :
select * from
(select emp_id, name, occupation,rank() over ( partition by occupation order by emp_id,RowNum) rank
from employee)
where rank <= 3
tested this in SQL Server (and it uses subquery)
select emp_id, name, occupation
from employees t1
where emp_id IN (select top 3 emp_id from employees t2 where t2.occupation = t1.occupation)
just do an ORDER by in the subquery to suit your needs
I'm not sure this is very efficient, but maybe a starting place?
select *
from people p1
join people p2
on p1.occupation = p2.occupation
join people p3
on p1.occupation = p3.occupation
and p2.occupation = p3.occupation
where p1.emp_id != p2.emp_id
and p1.emp_id != p3.emp_id
This should give you rows that contain 3 distinct employees all in the same occupation. Unfortunately, it will give you ALL combinations of those.
Can anyone pare this down please?