Domanda

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.

È stato utile?

Soluzione

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?

Altri suggerimenti

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top