Frage

I am working on a simple SQL query in SQL Developer and I can't seem to get this one right. My query is: "Retrieve the names of all the tutors that have had all their appointments with 'place name here'"

Here is my SQL:

SELECT Appt.TUTOR_USER_ID, TUTORS.FIRST_NAME, TUTORS.LAST_NAME
FROM Appt, 
  (
   SELECT Tutor.USER_ID, USR.FIRST_NAME, USR.LAST_NAME
   FROM Tutor, "USER" USR
   WHERE Tutor.USER_ID = USR.ID
  ) TUTORS,
  (
   SELECT USR.ID, USR.FIRST_NAME, USR.LAST_NAME
   FROM Student, "USER" USR
   WHERE Student.USER_ID = USR.ID
  ) USERS
WHERE (Appt.TUTOR_USER_ID = TUTORS.USER_ID AND Appt.STUDENT_USER_ID = USERS.ID)
  AND (USERS.FIRST_NAME = 'John' AND USERS.LAST_NAME = 'Smith');

What this gives me is all the tutors that has had appointments with John Smith, but not ALL THEIR appointments with John Smith. So I get tutors that have had appointments with other people besides John Smith. Can anyone give me a hand with the right statements or logic here.

War es hilfreich?

Lösung

You are only looking for records that match that name, so there is currently nothing to count or exclude anything else. You could add a and not exists clause that queries for any other students; or since it's in your question title, a minus that does the same:

select a.tutor_user_id, tu.first_name, tu.last_name
from appt a
join tutor t on t.user_id = a.tutor_user_id
join student s on s.user_id = a.student_user_id
join "USER" tu on tu.id = t.user_id
join "USER" su on su.id = s.user_id
where su.first_name = 'John' and su.last_name = 'Smith'
minus
select a.tutor_user_id, tu.first_name, tu.last_name
from appt a
join tutor t on t.user_id = a.tutor_user_id
join student s on s.user_id = a.student_user_id
join "USER" tu on tu.id = t.user_id
join "USER" su on su.id = s.user_id
where not (su.first_name = 'John' and su.last_name = 'Smith');

But I'd do it in one hit using group by and a having clause to count the number of appointments for John Smith and for anyone else:

select a.tutor_user_id, tu.first_name, tu.last_name
from appt a
join tutor t on t.user_id = a.tutor_user_id
join student s on s.user_id = a.student_user_id
join "USER" tu on tu.id = t.user_id
join "USER" su on su.id = s.user_id
group by a.tutor_user_id, tu.first_name, tu.last_name
having count(case when su.first_name = 'John' and su.last_name = 'Smith'
  then 1 else null end) > 0
and count(case when su.first_name = 'John' and su.last_name = 'Smith'
  then null else 1 end) = 0;

SQL Fiddle demo with some simple data, your original query (which also shows duplicates), and both these versions.

As JosephB and Serpiton mentioned, and I forgot to say, you don't actually need to go via the tutor or student tables as they aren't really adding anything; you can join to the "USER" table directly from the appt columns:

select a.tutor_user_id, tu.first_name, tu.last_name
from appt a
join "USER" tu on tu.id = a.tutor_user_id
join "USER" su on su.id = a.student_user_id
group by a.tutor_user_id, tu.first_name, tu.last_name
having count(case when su.first_name = 'John' and su.last_name = 'Smith'
  then 1 else null end) > 0
and count(case when su.first_name = 'John' and su.last_name = 'Smith'
  then null else 1 end) = 0;

As shown here.

In both cases I've reworked your original to use ANSI join syntax rather than the old Oracle style, and removed the subqueries while I was there as all the joining can be done on one level. I would strongly suggest, though, that you rethink your table names so you don't have to use a quoted identifier; maybe call them users, tutors and students instead?

Andere Tipps

Another way to go is to first filter the appointments to get the ones where the tutor only see a student, and use it as base for the main query

WITH tutor_appt As (
  SELECT tutor_user_id
       , MAX(student_user_id) student_user_id
  FROM   appt
  GROUP BY tutor_user_id
  HAVING COUNT(DISTINCT student_user_id) = 1
)
SELECT a.tutor_user_id, tu.first_name, tu.last_name
FROM   tutor_appt a
       INNER JOIN "USER" tu on tu.id = a.tutor_user_id
       INNER JOIN "USER" su on su.id = a.student_user_id
where  su.first_name = 'John' and su.last_name = 'Smith';

If the database is sane there is no need to join the Tutor and the Student table to appt before joining USER: the tutor_user_id and student_user_id columns can be joined to USER directly.

Here is an alternative that bypasses the TUTORS and the STUDENTS tables, because both Appt.TUTOR_USER_ID and Appt.STUDENT_USER_ID should exist as "USER".ID.

SELECT
  DISTINCT
  Appt.TUTOR_USER_ID, Tutors.FIRST_NAME, Tutors.LAST_NAME
FROM Appt
INNER JOIN
    (SELECT
     Appt.TUTOR_USER_ID, Users.FIRST_NAME, Users.LAST_NAME
    FROM Appt
    INNER JOIN "USER" Users
    ON Appt.TUTOR_USER_ID = Users.ID) Tutors
ON Appt.TUTOR_USER_ID = Tutors.TUTOR_USER_ID
INNER JOIN "USER" Users
ON Appt.STUDENT_USER_ID = Users.ID 
WHERE Users.FIRST_NAME = 'John' AND Users.LAST_NAME = 'Smith';

The tutors' names are obtained by joining with the USER table. Then, the Appt table is joined with the USER table again to filter for a given student's name.

Please see the SQL Fiddle demo.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top