sub query - list those that have the most - sql

I am trying to list the instructor of the Java courses that have been taught by the Instructor which has taught the most Java courses.
first I select Instructors that teach java courses
SELECT z.FIRST_NAME,z.LAST_NAME,b.COURSE_NO, MAX(b.DESCRIPTION) AS DESCRIPTION
FROM INSTRUCTOR z
JOIN SECTION w ON z.INSTRUCTOR_ID = w.INSTRUCTOR_ID
JOIN COURSE b ON w.COURSE_NO = b.COURSE_NO
WHERE DESCRIPTION like '%Java%'
GROUP BY z.FIRST_NAME,z.LAST_NAME,b.COURSE_NO
it displays results what I want so far
FIRST_NAME LAST_NAME COURSE_NO DESCRIPTION
------------------------- ------------------------- ---------- ---------------------------------
Tom Wojick 120 Intro to Java Programming
Gary Pertez 120 Intro to Java Programming
Anita Morris 124 Advanced Java Programming
Todd Smythe 122 Intermediate Java Programming
Charles Lowry 122 Intermediate Java Programming
Charles Lowry 120 Intro to Java Programming
Fernand Hanks 122 Intermediate Java Programming
etc...
but when I try to select the instructors which has taught the most Java courses I get totally different result
SELECT z.FIRST_NAME,z.LAST_NAME,b.COURSE_NO,b.DESCRIPTION
FROM INSTRUCTOR z
JOIN SECTION w ON z.INSTRUCTOR_ID = w.INSTRUCTOR_ID
JOIN COURSE b ON w.COURSE_NO = b.COURSE_NO
WHERE DESCRIPTION like '%Java%'
GROUP BY z.FIRST_NAME,z.LAST_NAME,b.COURSE_NO,b.DESCRIPTION
HAVING MAX(b.DESCRIPTION) =
(SELECT MAX(DESCRIPTION)
FROM (
SELECT z.FIRST_NAME,z.LAST_NAME,b.COURSE_NO, MAX(b.DESCRIPTION) AS DESCRIPTION
FROM INSTRUCTOR z
JOIN SECTION w ON z.INSTRUCTOR_ID = w.INSTRUCTOR_ID
JOIN COURSE b ON w.COURSE_NO = b.COURSE_NO
WHERE DESCRIPTION like '%Java%'
GROUP BY z.FIRST_NAME,z.LAST_NAME,b.COURSE_NO
)
)
ORDER BY z.FIRST_NAME,z.LAST_NAME,b.COURSE_NO,b.DESCRIPTION;
How could I count these description java courses and select max from it so it can only show one instructor with all his Java courses ?

Perhaps something like:
WITH q AS (
SELECT z.FIRST_NAME,z.LAST_NAME,b.COURSE_NO, MAX(b.DESCRIPTION) AS DESCRIPTION
, COUNT(*) OVER (PARTITION BY z.FIRST_NAME, z.LAST_NAME) TheCount
FROM INSTRUCTOR z
JOIN SECTION w ON z.INSTRUCTOR_ID = w.INSTRUCTOR_ID
JOIN COURSE b ON w.COURSE_NO = b.COURSE_NO
WHERE UPPER(DESCRIPTION) like '%JAVA%'
GROUP BY z.FIRST_NAME,z.LAST_NAME,b.COURSE_NO
)
SELECT first_name, last_name, course_no, description
FROM q
WHERE TheCount = (SELECT MAX(TheCount) FROM q);
Note this may give you more than one instructor.
Also, I've compared the course description search string using all upper case to eliminate the possibility of missing a course because of JAVA being in a different case.
EDIT:
There are a number of SQL functions that can be used as "Analytic" functions - where the function is applied to the records that fall within a "window", i.e., the PARTITION BY clause. Some of them are sensitive to ordering as well, and an ORDER BY clause can be used as well. You can find some good reference material here at Tim Hall's site. Another is at Shouvik Basu's blog.

Related

Nesting SELECT statements with duplicate entries and COUNT

I'm working with 3 tables: actors, films, and actor_film. Actors and films only have 2 fields: id (primary key) and name. Actor_film also has 2 fields, actor and film, which are both foreign keys representing actor and film ids, respectively. So if a film had 4 actors in it, there'd be 4 actor_film entries with the same film and 4 different actors.
My problem is that, given a certain actor's id, I'd like to return the actor id, actor name, film name, and the total number of actors in that film. However, the only actors that I want to show are ones that contain certain letters in their names.
Let me clear things up with an example. Say Tom Hanks is in only 2 movies, Forrest Gump and Saving Private Ryan, and I'm looking for actors in those 2 movies that have "Gary" or "Matt" in their names. Further suppose that there are 4 actors in Forrest Gump, and 5 in Saving Private Ryan. Then, the only thing I'd want to return would be (without the column names, of course)
actor id | actor name | film name | # actors
abcdefg | Gary Sinise | Forrest Gump | 4
hijklmn | Matt Damon | Saving Private Ryan | 5
opqrstu | Paul Giamatti | Saving Private Ryan | 5
Currently, I'm 75% of the way there by using:
SELECT actor.id, actors.name, films.name,
FROM (
SELECT actor_film.film
FROM actor_film, actors
WHERE actor_film.actor = actors.id
) AS a, actor_film, actors, films
WHERE actor_film.film = a.film
AND actors.id = actor_film.actor
AND films.id = a.film;
This is returning stuff like:
arnie | Arnold Schwarzenegger | Around the World in 80 Days
arnie | Arnold Schwarzenegger | Around the World in 80 Days
for a film that has 2 actors in it. In other words, I can't pull out all the distinct actors in the movie, but get the proper count for it implicitly and not explicitly with COUNT.
Anyway, I think I'm looking for some kind of INNER JOIN or nested SELECT, but I'm new to SQLite3 and don't know how to bring these together. Any solutions would be great, and any explanations on top of that would be amazing as well.
You shouldn't use the old style joins. They were old in '95 when the newer standard that let you do left joins clearer was made a standard.
I've noticed you also use plurals for your table names (eg "actors") The standard style is to use the singular for the table table name (eg "actor")
I use both these suggestions below, I also show you each step. I suggest you run the queries for each step and look at the output to understand how everything works since you are new to SQL.
Ok, lets take you problem step by step. First of all to see each actor and the films they are in (your first 3 columns) do this:
SELECT a.id as actor_id, a.name as actor_name, f.name as film_name
FROM actor as a
JOIN actor_film af on a.id = af.actor
JOIN film as f on af.film = f.id
Your last column can be found with the following query:
SELECT af.film as film_id, count(*) as c
FROM actor_film as af
GROUP BY af.film
Now we just join them together
SELECT a.id as actor_id, a.name as actor_name, f.name as film_name, fc.c as num_actors
FROM actor as a
JOIN actor_film af on a.id = af.actor
JOIN film as f on af.film = f.id
JOIN (
SELECT af.film as film_id, count(*) as c
FROM actor_file as af
GROUP BY af.film
) as fc on af.film = fc.film_id
If you want you can add a
WHERE a.name = 'Gary' OR a.name = 'Matt'
depending on your platform you might want
WHERE lower(a.name) = 'gary' OR lower(a.name) = 'matt'

SQL inquiry, tried absolutely everything I know, sql, four tables

I cant get this inquiry, tried like every thing
TABLE: ARTIST
JMBG NAME AGE ADRESA
--------------------------------------
J1 Ladygaga 35 HOLIVUDHILZ
J2 DUSKO 13 BB
J3 EMINEM 40 REVOLUCIJA 5
J4 BAGI 22 KURAC
J5 MARKO 33 ULICA
TABLE:HALL
DID CAPACITY CITY
---------------------------------
D1 500 PODGORICA
D2 300 NIS
D3 1000 BAR
D4 2000 NEWYORK
D5 750 BEOGRAD
TABLE: CITY
-----------------------------------------
BAR montenegro 5000
BEOGRAD Serbia 2000000
BUDVA montenegro 50000
NEWYORK AMERICA 7000000
NIS Serbia 1000000
PODGORICA montenegro 250000
TABLE: CONCERT
ID JMBG HALL
------------------------
K1 J3 D4
K2 J4 D1
K3 J1 D1
K4 J1 D5
K5 J1 D1
K6 J3 D1
K7 J5 D1
The inquiry is: Find the countries where the artist with the most held concerts
has performed in. I really did spend a lot of time on this and energy. I would greatly appreciate if someone could do this that has experience, and doesnt find it too difficult.
I tried this:
SELECT DISTINCT COUNTRY FROM CITY G, HALL D, CONCERT K
WHERE K.DID = D.DID AND D.NAZIV = G.NAZIV AND EXISTS(
SELECT JMBG FROM CONCERT K1,HALL D1, CITY G1
WHERE K.KID=K1.KID
GROUP BY JMBG
HAVING COUNT (*) >= ALL(SELECT COUNT(*) FROM CONCERT
GROUP BY JMBG))
Break it down. The artist with the most held concerts... Which artist had the most held concerts? (We're going to assume that we're interested in the total number of concerts held overall (in all countries), not the number of concerts held in a particular country.
How many concerts did each artist hold?
SELECT c.jmbg
, COUNT(1) AS cnt
FROM concert c
GROUP BY c.jmbg
Which artist held the most concerts? MySQL and MS SQL Server both have some convenient short cuts we can use here. A question we should ask here, what if there are two or more artists held the same number of concerts? Do we want to return both (or all) of those artists, or just return one of them? Which one? (We'd prefer the query to be deterministic... to return the same result given the same rows in the tables.)
Assuming that we want to return just one artist that held the most concerts...
For MySQL:
SELECT c.jmbg
FROM concert c
GROUP BY c.jmbg
ORDER BY COUNT(1) DESC, c.jmbg DESC
LIMIT 1
For SQL Server:
SELECT TOP 1 c.jmbg
FROM concert c
GROUP BY c.jmbg
ORDER BY COUNT(1) DESC, c.jmbg DESC
So that gets us the artist.
The other part of the "inquiry"... which countries did the artist hold concerts in.
Given a particular artist, we could write a query that performs join operations on the concert, hall and city tables. We'll just take a guess at the name of that first column in the city table (since it isn't provided in the question).
SELECT i.country
FROM city i
JOIN hall h
ON h.city = i.cid
JOIN concert o
ON o.hall = h.did
WHERE o.jmbg = 'Ladygaga'
GROUP BY i.country
To combine the two queries, we could use the first as a subquery. My preference is to use an inline view.
SELECT g.country
FROM city g
JOIN hall h
ON h.city = g.cid
JOIN concert o
ON o.hall = h.did
JOIN (
SELECT c.jmbg
FROM concert c
GROUP BY c.jmbg
ORDER BY COUNT(1) DESC, c.jmbg DESC
LIMIT 1
) m
ON m.jmbg = o.jmbg
GROUP BY g.country
Obviously, there are obviously other query patterns that will return an equivalent result.
As I noted in a comment on the question, the specification for this "inquiry" is a bit ambiguous, as to what is meant by "where the artist with the most held concerts has performed in".
There is another interpretation of that specification. If we're interested in getting and analyzing a count of "how many concerts were held in each country by each artist", that's a different query.
FOLLOWUP
"... not allowed to use TOP DESC"
Then just write the query differently. Here's a different way to get the "largest number of concerts held by any artist", and use that to get all the artists that all held that number of concerts.
SELECT n.jmbg
FROM ( -- largest number of concerts by artist
SELECT MAX(p.cnt) AS maxcnt
FROM (
SELECT COUNT(1) AS cnt
FROM concert d
GROUP BY d.jmbg
) p
) o
JOIN ( -- count of concerts by artist
SELECT c.jmbg
, COUNT(1) AS cnt
FROM concert c
GROUP BY c.jmbg
) n
ON n.cnt = o.maxcnt
Since that has the potential to return more than one row (more than one artist), your outer query may want to return a list of countries for each of the returned artists. That is to say, rather than just GROUP BY g.country, you'll likely want to return the artist in the SELECT list, and
GROUP BY m.jmbg, g.country
ORDER BY m.jmbg, g.country
This is a basic question that looks like coming out of school type of question. This answer will give you some hints but you need to work it out for yourself.
JOIN is your friend, find source below:
JOIN - MySQL
JOIN - SQL Server
What you need to do:
join CONCERT table with HALL table by HALL ID
join HALL table to CITY table by CITY name
sum the count of country appearance or hall capacity (either one you need) grouped by artist
order descending by the sum of count if you need it
Good luck

SQL query get course number for certain student grades

I'm working through some problems and I can't seem to get the expected results for this one. The question is below with what is in my code right now and also the expected results. If anyone help that would be great. I'm just trying to get a understanding on this and can't seem to get my head around what exactly this is asking as you can see my code I have now isn't close to what the expected result is as of right now. Also I added the schema this will show whats in what table if needed for your guys help.
Question:
List the course number of courses wherein students have received grades for every one of the possible defined grade types. Order by course number.
My code so far:
SELECT g.Student_id, g.Grade_type_code
FROM Grade g LEFT OUTER JOIN Section s
ON g.Section_id = s.Section_id
GROUP BY g.Student_id, g.Grade_type_code
ORDER BY g.Student_id;
Any help would be great, also here is the Schema.
DBMS: I'm using Oracle SQL Developer
Here is the Expected Result
COURSE_NO
----------
20
25
100
120
122
125
130
135
Note: The Chapter for this problem is based off using
LEFT OUTER JOIN
My Current results
STUDENT_ID GRADE_TYPE_CODE
---------- ---------------
102 FI
102 HM
102 MT
102 PA
102 QZ
103 FI
103 HM
103 MT
103 PA
103 QZ
104 FI
104 HM
Based on your ER diagram I believe this query should return a list of courses whose enrolled students have collectively received all of the grade types listed in the GRADE_TYPE table.
select s.course_no,
c.descr,
count(distinct g.grade_type_code) as num_grade_types
from grade g
join enrollment e
on g.student_id = e.student_id
and g.section_id = e.section_id
join section s
on e.section_id = s.section_id
join course c
on s.course_no = c.course_no
group by s.course_no, c.descr
having count(distinct g.grade_type_code) = (select count(grade_type_code)
from grade_type)
I didn't notice your expected result was only the course # (you can just get rid of the columns you don't want from the select list). Also the join to the COURSE table is only there to get the course description, so if you don't want the course description selected, you do not need that join.
You need to select COURSE_NO instead. And also use JOIN and not LEFT JOUTER JOIN.
Something like this:
select COURSE_NO from
(
SELECT distinct (s.COURSE_NO)
FROM Grade g JOIN Section s
ON g.Section_id = s.Section_id
)
ORDER BY s.COURSE_NO;

A Query of certain students in a certain room

I'm working through a problem, working with SQL Oracle. For some reason I am getting the result of 13 for instructors teaching that course when I should only be getting 1. There is only 1 Instructor that teaches only 3 or more students in that room.
Question:
Create a query to determine the number of instructors who have taught more than 3 students in a course section taught in Room L211.
My Code:
SELECT COUNT(Instructor_Id) AS NumberOfInstructors
FROM Section s, Enrollment e
WHERE s.Section_Id = e.Section_Id
AND Location = 'L211'
HAVING COUNT(Student_Id) =
(SELECT COUNT(Student_iD)
FROM Section s, Enrollment e
WHERE s.Section_Id = e.Section_Id
AND Location = 'L211')
ORDER BY s.Course_No;
My Results:
NUMBEROFINSTRUCTORS
-------------------
13
Expected Results:
NUMBEROFINSTRUCTORS
-------------------
1
So I feel like Im kind of going in the right direction maybe not but I feel like it is adding all the instructors up that teach in that class. Ive messed around a lot with that code I've given so if anyone can point me in the right direction. I'm guessing I need to have a count for counting the students who have been in a class with that room number I think I've tried that and got a result of 4 so I'm not sure.
Any help would be great, also here is the Schema.
DBMS: I'm using Oracle SQL Developer
try this:
SELECT COUNT(*) NumberOfInstructors
From (Select Instructor_Id
FROM Section s
join Enrollment e
on e.Section_Id = s.Section_Id
WHERE s.Location = 'L211'
Group By Instructor_Id
HAVING COUNT(Student_Id) >= 3) Z
I think you are needing a subquery here. In pseudo code, you are looking for a query like:
SELECT count(teachers)
FROM
(SELECT count(students) FROM enrollment where location='L211') AS "numberofstudents"
WHERE numberofstudents > 3
I did not test the code, but it seems to me you are missing a condition. Your question requires you to find "Instructor that teaches only 3 or more students in that room", but you did not have any condition to check for student number > 3. Try this:
SELECT COUNT(Instructor_Id) AS NumberOfInstructors
FROM Section s, Enrollment e
WHERE s.Section_Id = e.Section_Id
AND Location = 'L211'
HAVING COUNT(Student_Id) =
(SELECT COUNT(Student_iD)
FROM Section s, Enrollment e
WHERE s.Section_Id = e.Section_Id
AND Location = 'L211' AND COUNT(Student_Id) > 3)
ORDER BY s.Course_No;

sql select records with matching subsets

There are two sets of employees: managers and grunts.
For each manager, there's a table manager_meetings that holds a list of which meetings each manager attended. A similar table grunt_meetings holds a list of which meetings each grunt attended.
So:
manager_meetings grunt_meetings
managerID meetingID gruntID meetingID
1 a 4 a
1 b 4 b
1 c 4 c
2 a 4 d
2 b 5 a
3 c 5 b
3 d 5 c
3 e 6 a
6 c
7 b
7 a
The owner doesn't like it when a manager and a grunt know exactly the same information. It makes his head hurt. He wants to identify this situation, so he can demote the manager to a grunt, or promote the grunt to a manager, or take them both golfing. The owner likes to golf.
The task is to list every combination of manager and grunt where both attended exactly the same meetings. If the manager attended more meeting than the grunt, no match. If the grunt attended more meetings than the manager, no match.
The expected results here are:
ManagerID GruntID
2 7
1 5
...because manager 2 and grunt 7 both attended (a,b), while manager 1 and grunt 5 both attended (a,b,c).
I can solve it in a clunky way, by pivoting up the subset of meetings in a subquery into XML, and comparing each grunt's XML list to each manager's XML. But that's horrible, and also I have to explain to the owner what XML is. And I don't like golfing.
Is there some better way to do "WHERE {subset1} = {subset2}"? It feels like I'm missing some clever kind of join.
SQL Fiddle
Here is a version that works:
select m.mId, g.gId, count(*) --select m.mid, g.gid, mm.meetingid, gm.meetingid as gmm
from manager m cross join
grunt g left outer join
(select mm.*, count(*) over (partition by mm.mid) as cnt
from manager_meeting mm
) mm
on mm.mid = m.mId full outer join
(select gm.*, count(*) over (partition by gm.gid) as cnt
from grunt_meeting gm
) gm
on gm.gid = g.gid and gm.meetingid = mm.meetingid
group by m.mId, g.gId, mm.cnt, gm.cnt
having count(*) = mm.cnt and mm.cnt = gm.cnt;
The string comparison method is shorter, perhaps easier to understand, and probably faster.
EDIT:
For your particular case of getting exact matches, the query can be simplified:
select mm.mId, gm.gId
from (select mm.*, count(*) over (partition by mm.mid) as cnt
from manager_meeting mm
) mm join
(select gm.*, count(*) over (partition by gm.gid) as cnt
from grunt_meeting gm
) gm
on gm.meetingid = mm.meetingid and
mm.cnt = gm.cnt
group by mm.mId, gm.gId
having count(*) = max(mm.cnt);
This might be more competitive with the string version, both in terms of performance and clarity.
It counts the number of matches between a grunt and a manager. It then checks that this is all the meetings for each.
An attempt at avenging Aaron's defeat – a solution using EXCEPT:
SELECT
m.mID,
g.gID
FROM
manager AS m
INNER JOIN
grunt AS g
ON NOT EXISTS (
SELECT meetingID
FROM manager_meeting
WHERE mID = m.mID
EXCEPT
SELECT meetingID
FROM grunt_meeting
WHERE gID = g.gID
)
AND NOT EXISTS (
SELECT meetingID
FROM grunt_meeting
WHERE gID = g.gID
EXCEPT
SELECT meetingID
FROM manager_meeting
WHERE mID = m.mID
);
Basically, subtract a grunt's set of meetings from a manager's set of meetings, then the other way round. If neither result contains rows, the grunt and the manager attended the same set of meetings.
Please note that this query will match managers and grunts that never attended a single meeting.
An alternative version - but requires another table. Basically, we give each meeting a distinct power of two as it's 'value', then sum every manager's meeting value and each grunt's meeting value. Where they're the same, we have a match.
It should be possible to make the meeting_values table a TVF, but this is a little bit simpler.
SQL Fiddle
Additional table:
CREATE TABLE meeting_values (value INT, meetingID CHAR(1));
INSERT INTO meeting_values VALUES
(1,'a'),(2,'b'),(4,'c'),(8,'d'),(16,'e');
And the query:
SELECT managemeets.mID, gruntmeets.gID
FROM
( SELECT gm.gID, sum(value) AS meeting_totals
FROM grunt_meeting gm
INNER JOIN
meeting_values mv ON gm.meetingID = mv.meetingID
GROUP BY gm.gID
) gruntmeets
INNER JOIN
( SELECT mm.mID, sum(value) AS meeting_totals
FROM manager_meeting mm
INNER JOIN
meeting_values mv ON mm.meetingID = mv.meetingID
GROUP BY mm.mID
) managemeets ON gruntmeets.meeting_totals = managemeets.meeting_totals