These are the tables:
professor:
+-------+--------+--------+--------+------+
| empid | name | status | salary | age |
+-------+--------+--------+--------+------+
| 1 | Arun | 1 | 2000 | 23 |
| 2 | Benoy | 0 | 3000 | 25 |
| 3 | Chacko | 1 | 1000 | 36 |
| 4 | Divin | 0 | 5000 | 32 |
| 5 | Edwin | 1 | 2500 | 55 |
| 7 | George | 0 | 1500 | 46 |
+-------+--------+--------+--------+------+
works:
+----------+-------+---------+
| courseid | empid | classid |
+----------+-------+---------+
| 1 | 1 | 10 |
| 2 | 2 | 9 |
| 3 | 3 | 8 |
| 4 | 4 | 10 |
| 5 | 5 | 9 |
| 6 | 1 | 9 |
| 2 | 3 | 10 |
| 2 | 1 | 7 |
+----------+-------+---------+
course:
+----------+------------+--------+
| courseid | coursename | points |
+----------+------------+--------+
| 1 | Maths | 100 |
| 2 | Science | 80 |
| 3 | English | 85 |
| 4 | Social | 90 |
| 5 | Malayalam | 99 |
| 6 | Arts | 40 |
+----------+------------+--------+
The question is :
Return list of employees who have taught course Maths or Science but
not both
The query which I wrote is :
select distinct professor.name from professor
inner join works
on professor.empid=works.empid
where works.courseid in
(select courseid from course where coursename ='Maths' or coursename='Science');
The output I received is:
Arun
Benoy
Chacko
Here the employee 'Arun' shouldnt have been displayed as he as taught both Maths and Science.
Please help me out !!
You may use an aggregate COUNT() to check that the total number of DISTINCT courses taught is exactly 1, while still filtering to the two different types of courses. That ensures that only one, never both, is returned.
Because the IN () limits all rows initially returned only to the two desired courses, professors can have a maximum of 2 possible different courses via COUNT(DISTINCT coursename). A HAVING clause then prohibits those with 2 from the final result set.
SELECT
DISTINCT professor.name
FROM
professor
INNER JOIN works ON professor.empid = works.empid
/* Join against course to get the course names */
INNER JOIN course ON works.courseid = course.courseid
WHERE
/* Restrict only to Maths, Science */
course.coursename IN ('Maths', 'Science')
GROUP BY professor.name
/* Only those with exactly one type of course */
HAVING COUNT(DISTINCT course.coursename) = 1
Here is a demonstration: http://sqlfiddle.com/#!2/2e9610/2
You want to use an xor here instead of an or.
select distinct professor.name from professor
inner join works
on professor.empid=works.empid
where works.courseid in
(select courseid from course where coursename ='Maths' xor coursename='Science');
Related
I have 3 tables: NAMES, REGISTRATIONS, and RENEWALS. I'm using LEFT JOIN to join the 3 tables with a common ID.
I need to count the number of REGISTRATIONS of each user, as well as the number of RENEWALS. I've tried using different options in the GROUP BY field, but none seemed to work.
Here's the SELECT statement:
SELECT
names.name_id AS 'Names ID'
,names.name AS Name
,count(registrations.date) AS Registrations
,count(renewals.date) AS Renewals
FROM names
LEFT JOIN registrations
ON names.name_id = registrations.name_id
LEFT JOIN renewals
ON renewals.name_id = registrations.name_id
GROUP BY names.name_id, registrations.name_id, renewals.name_id;
And here are the 3 tables:
TABLE: names
+---------+------+
| name_id | name |
+---------+------+
| 1 | Ana |
| 2 | John |
| 3 | Paul |
+---------+------+
TABLE: registrations
+-----------------+---------+---------------------+-------+
| registration_id | name_id | date | value |
+-----------------+---------+---------------------+-------+
| 1 | 1 | 2014-01-30 13:15:02 | 15 |
| 2 | 2 | 2014-05-01 18:01:44 | 15 |
| 3 | 2 | 2014-07-08 15:10:43 | 20 |
| 4 | 3 | 2012-09-28 17:45:32 | 15 |
| 5 | 3 | 2014-01-09 18:26:14 | 20 |
| 6 | 3 | 2015-01-10 13:22:01 | 25 |
+-----------------+---------+---------------------+-------+
TABLE: renewals
+------------+---------+---------------------+-------+
| renewal_id | name_id | date | value |
+------------+---------+---------------------+-------+
| 1 | 1 | 2015-01-30 00:00:00 | 5 |
| 2 | 1 | 2016-02-12 00:00:00 | 5 |
| 3 | 1 | 2015-06-01 00:00:00 | 5 |
| 4 | 1 | 2013-11-24 00:00:00 | 5 |
| 5 | 2 | 2015-01-27 00:00:00 | 5 |
+------------+---------+---------------------+-------+
Here's the INCORRECT result I'm getting:
+----------+------+---------------+----------+
| Names ID | Name | Registrations | Renewals |
+----------+------+---------------+----------+
| 1 | Ana | 4 | 4 |
| 2 | John | 2 | 2 |
| 3 | Paul | 3 | 0 |
+----------+------+---------------+----------+
The CORRECT result I was expecting would be:
+----------+------+---------------+----------+
| Names ID | Name | Registrations | Renewals |
+----------+------+---------------+----------+
| 1 | Ana | 1 | 4 |
| 2 | John | 2 | 1 |
| 3 | Paul | 3 | 0 |
+----------+------+---------------+----------+
How can I fix the query to get a correct result?
Try this:
SELECT
names.name_id AS 'Names ID'
,names.name AS Name
,count(distinct registrations.registration_id) AS Registrations
,count(distinct renewals.renewal_id) AS Renewals
FROM names
LEFT JOIN registrations
ON names.name_id = registrations.name_id
LEFT JOIN renewals
ON renewals.name_id = registrations.name_id
GROUP BY names.name_id, registrations.name_id, renewals.name_id;
Whenever I run into this type of issue, I find it helps to just run a select * query if your server can take it. Like this:
SELECT *
FROM names
LEFT JOIN registrations
ON names.name_id = registrations.name_id
LEFT JOIN renewals
ON renewals.name_id = registrations.name_id ;
That will let you see what you are really counting.
Your query is executed just fine.
After the first join you have 1 entry for Ana, 2 entries for John and 3 for Paul.
After the seconds join the one entry for Ana is duplicated 4 times and joined (concatenated) with the 4 renewals. If you now count the registration dates for Ana you get 4. That is where your "errors" come from.
You could for example count the distinct dates to fix it.
Consider this data:
---+-----------+-------+--+
| id | name | grade | |
+----+-----------+-------+--+
| 13 | Maria | 10 | |
| 18 | Lorenzo | 10 | |
| 2 | Cissa | 10 | |
| 3 | Neto | 9 | |
| 15 | Gabriel | 9 | |
| 10 | Laura | 9 | |
| 12 | JoĆ£ozinho | 8 | |
| 16 | Sergio | 8 | |
| 8 | Adriele | 8 | |
| 6 | Jorgito | 8 | |
| 5 | Aline | 8 | |
| 1 | Cintia | 8 | |
| 19 | Fabiana | 7 | |
| 11 | Vinicius | 7 | |
| 9 | Tatiane | 7 | |
| 7 | Chico | 7 | |
| 4 | Marcos | 7 | |
| 14 | Pedro | 6 | |
| 17 | Mauricio | 6 | |
| 20 | Leonardo | 6 | |
+----+-----------+-------+--+
I need the students with the three biggest grades. I think I need to group the data by grade and limit to the top 3 groups.
"SELECT * FROM student GROUP BY grade LIMIT 3" only gives me 3 rows, that's not what I want.
I've tried to use HAVING to filter the groups, but without success. I don't want to set the filter grade>MAX(grade)-2, because theoretically I will not know the grades. But this filter didn't work anyway.
I'am using MySQL. Please help!
You can do this using a join:
select s.*
from student s join
(select grade
from student
group by grade
order by grade desc
limit 3
) g3
on s.grade = g3.grade;
In most databases, you an do this using in:
select s.*
from student s
where s.grade in (select grade
from student
group by grade
order by grade desc
limit 3
);
However, MySQL seems to reject this syntax.
select s1.*
from students s1
join (select distinct grade from students
order by grade desc limit 3) s2 on s1.grade = s2.grade
Alternatively:
select *
from students
where grade >= (select distinct grade from students
order by grade desc limit 2,1)
select s.*
from student s join
(select top 3 grade
from student
group by grade
order by grade desc
) g3
on s.grade = g3.grade;
These are the tables:
professor
+-------+--------+--------+--------+------+
| empid | name | status | salary | age |
+-------+--------+--------+--------+------+
| 1 | Arun | 1 | 2000 | 23 |
| 2 | Benoy | 0 | 3000 | 25 |
| 3 | Chacko | 1 | 1000 | 36 |
| 4 | Divin | 0 | 5000 | 32 |
| 5 | Edwin | 1 | 2500 | 55 |
| 7 | George | 0 | 1500 | 46 |
+-------+--------+--------+--------+------+
works
+----------+-------+---------+
| courseid | empid | classid |
+----------+-------+---------+
| 1 | 1 | 10 |
| 2 | 2 | 9 |
| 3 | 3 | 8 |
| 4 | 4 | 10 |
| 5 | 5 | 9 |
| 6 | 1 | 9 |
+----------+-------+---------+
The above are the tables from which I need to retrieve the data from.
The question is to return list of Employees who take both Class 10 and
Class 9.
The query I have written is:
select professor.name
from inner join works
on professor.empid=works.empid
where works.classid=9 and works.classid=10;
I know that the result I want is Arun, but I don't know what should be the exact query to retrieve the required result.
He wants the professeors that take class 9 AND 10. So there are 2 different records in works that need to match.
select professor.name from professor
join works A on A.empid=professor.empid and A.classid=9
join works B on B.empid=professor.empid and B.classid=10
See http://sqlfiddle.com/#!2/4be88a/1
Try This
select professor.name
from professor inner join works
on professor.empid=works.empid
where works.classid=9 OR works.classid=10;
(OR)
select professor.name
from professor inner join works
on professor.empid=works.empid
where works.classid IN ('9','10')
SELECT prof.name FROM professor AS prof
JOIN works
ON prof.empid = works.empid
WHERE works.classid IN (9, 10);
Given a *students_exam_rooms* table:
+------------+---------+---------+
| student_id | room_id | seat_no |
+------------+---------+---------+
| 1 | 30 | 1001 |
| 2 | 30 | 1002 |
| 3 | 31 | 2001 |
| 4 | 32 | 2002 |
| 5 | 33 | 3001 |
| 6 | 33 | 3002 |
| 7 | 34 | 4001 |
| 8 | 34 | 4002 |
+------------+---------+---------+
And *students_tbl*:
+------------+-------------+------+
| student_id | studen_name | year |
+------------+-------------+------+
| 1 | Eric | 1 |
| 2 | Mustafa | 1 |
| 3 | Michael | 2 |
| 4 | Andy | 2 |
| 5 | Rafael | 3 |
| 6 | Mark | 3 |
| 7 | Jack | 4 |
| 8 | peter | 4 |
+------------+-------------+------+
How can I select from *students_exam_rooms* ordering by *students_tbl.year* but with one after one like this:
+--------------+------+
| student_name | year |
+--------------+------+
| Eric | 1 |
| Michael | 2 |
| Rafael | 3 |
| Jack | 4 |
| Mustafa | 1 |
| Andy | 2 |
| Mark | 3 |
| Peter | 4 |
+--------------+------+
I'm assuming that you want to order by the "occurrence-count" of the year then the year, e.g. all the first-occurrences of all years first, sorted by year, then all second-occurrences of all years also sorted by year, and so on. That would be a perfect case for emulating other RDBMS' analytic / windowing functions:
select *
from (
select
s.studen_name,
s.year,
ser.*,
(
select 1 + count(*)
from students_tbl s2
where s.year = s2.year
and s.student_id > s2.student_id
) rank
from students_tbl s
JOIN students_exam_rooms ser
ON s.student_id = ser.student_id
) i_dont_really_want_to_name_this
order by rank, year
Here it is against a slightly tweaked version of JW's fiddle: http://www.sqlfiddle.com/#!2/27c91/1
Emulating Analytic (AKA Ranking) Functions with MySQL is a good article that gives more background and explanation.
try any of these below:
SELECT a.studen_name, a.year
FROM students_tbl a
INNER JOIN students_exam_rooms b
ON a.student_id = b.student_id
ORDER BY REVERSE(b.seat_no),
a.year
SQLFiddle Demo
by using Modulo
SELECT a.studen_name, a.year
FROM students_tbl a
INNER JOIN students_exam_rooms b
ON a.student_id = b.student_id
ORDER BY CASE WHEN MOD(b.seat_no, 2) <> 0 THEN 0 ELSE 1 END,
a.year
SQLFiddle Demo
Looks to me like you're trying to sort first by seat and then by year. Looking at your students_exam_rooms table, it looks like you started with a simple seat number and prepended year * 1000. So, if we omit the year, it looks like this:
> select * from fixed_students_exam_rooms;
+------------+---------+---------+
| student_id | room_id | seat_no |
+------------+---------+---------+
| 1 | 30 | 1 |
| 2 | 30 | 2 |
| 3 | 31 | 1 |
| 4 | 32 | 2 |
| 5 | 33 | 1 |
| 6 | 33 | 2 |
| 7 | 34 | 1 |
| 8 | 34 | 2 |
+------------+---------+---------+
And if you had that table, your query is simple:
select
student_name, year
from
modified_student_exame_rooms
left join students_tbl using (student_id)
order by
seat_no, year
;
Using the table as you currently have it, it's only slightly more complicated, assuming the "core seat number" doesn't excede 999.
select
student_name, year
from
modified_student_exame_rooms
left join students_tbl using (student_id)
order by
convert(substr(seat_no, 2), unsigned),
year
;
I have three existing SQL tables we will call "teams", "miles", and "riders". Leaving out the fluff, their structure looks like this:
Table: teams
------------+-------------+---------+
| team_name | captains_id | team_id |
------------+-------------+---------+
| superbads | 11 | 1 |
| superflys | 12 | 2 |
------------+-------------+---------+
Table: riders
--------------+-----------+----------+
| rider_name | team_id | rider_id |
--------------+-----------+----------+
| donatello | 1 | 10 |
| leonardo | 1 | 11 |
| michelangelo| 2 | 12 |
| raphael | 2 | 13 |
--------------+-----------+----------+
Table: miles
--------------+-----------+----------+
| rider_id | miles | id |
--------------+-----------+----------+
| 10 | 100 | 1 |
| 10 | 62 | 2 |
| 11 | 110 | 3 |
| 11 | 100 | 4 |
| 12 | 8 | 5 |
| 12 | 22 | 6 |
| 13 | 29 | 7 |
| 13 | 2 | 8 |
--------------+-----------+----------+
I need to return a list of teams with total miles generated by that team (I also need to return the team captain's name, but that's a bit easier).
The difficulty is that I need to join miles on riders, sum the "miles" field, and then join that on teams somehow.
Changing the table structure is pretty much out, as this is an existing application. This is a LAMP environment, so manipulating PHP arrays after the query is an option if needed.
This should do it:
select t.team_id, t.team_name, t.captains_id, sum(m.miles) as total_miles
from teams t
inner join riders r on r.team_id = t.team_id
inner join miles m on m.rider_id = r.rider_id
group by t.team_id, t.team_name, t.captains_id