SQL inline view subquery

2.3k Views Asked by At

Is it possible to reference an inline view defined in the "FROM" clause from a subquery in the WHERE clause?

SELECT tmp.TeacherName,
       tmp.courseid,
       tmp.AvgAttendingStudents
FROM   (SELECT T.TeacherID              AS ID,
               T.TeacherName            AS Name,
               C.CourseID               AS CourseID,
               avg(L.AttendingStudents) AS AvgAttendingStudents
        FROM   Teachers AS T
               join Courses AS C
                 ON C.TeacherID = T.TeacherID
               join Lessons AS L
                 ON L.CourseID = C.CourseID
        GROUP  BY T.TeacherID,
                  C.CourseID) AS tmp
WHERE  tmp.AvgAttendingStudents = (SELECT max(AvgAttendingStudents)
                                   FROM   tmp AS tmp2
                                   WHERE  tmp2.TeacherID = tmp.TeacherID);  

In this example i'm trying to list all the teachers and for each of them I want to show the course having the maximum average of attending students (calculated on all lessons). I tried to use an inline view (tmp) to calculate the average number of attending students for each course, but I don't know if I can reference that view in the subquery SELECT max(...). I need this to work with Oracle, but unfortunately at the moment I don't have any Oracle database to try it. I tried with MySQL (since I don't think I'm using any Oracle-specific features), but as expected I get the error "Table 'db.tmp' doesn't exist". Is this somehow possible with Oracle?

Here's my example schema:

CREATE TABLE Courses
  (
     CourseID   INTEGER PRIMARY KEY,
     CourseName VARCHAR(32),
     TeacherID  INTEGER
  );

CREATE TABLE Teachers
  (
     TeacherID   INTEGER PRIMARY KEY,
     TeacherName VARCHAR(32)
  );

CREATE TABLE Lessons
  (
     LessonDate        TIMESTAMP,
     CourseID          INTEGER,
     AttendingStudents INTEGER,
     PRIMARY KEY (LessonDate, CourseID)
  );  

(Sorry for my bad english)

2

There are 2 best solutions below

0
On BEST ANSWER

You are right in that you can't reference the derived table ("inline view") that way. You need to rewrite the derived table ("inline view") to a common table expression:

You also have other errors in there. In the derived table you rename TeacherID to ID and TeacherName to Name so you need to use those column names not the "real" ones.

Also Oracle doesn't support AS for a table alias, so you need to get rid of those as well.

So a direct rewrite of the statement would be:

with tmp as (
  SELECT T.TeacherID              AS ID,
         T.TeacherName            AS Name,
         C.CourseID               AS CourseID,
         avg(L.AttendingStudents) AS AvgAttendingStudents
  FROM   Teachers T
         join Courses C
           ON C.TeacherID = T.TeacherID
         join Lessons L
           ON L.CourseID = C.CourseID
  GROUP  BY T.TeacherID,
            C.CourseID
)
SELECT tmp.name,
       tmp.courseid,
       tmp.AvgAttendingStudents
FROM tmp
where tmp.AvgAttendingStudents = (SELECT max(AvgAttendingStudents)
                                  FROM   tmp tmp2
                                  WHERE  tmp2.id = tmp.id);

However the above will not work in Oracle because of the invalid use of the group by and aggregate function. The above will result in "ORA-00979: not a GROUP BY expression", see this SQLFiddle

For this to work you need to use a window function in the CTE and get rid of the group by:

with tmp as (
  SELECT T.TeacherID              AS ID,
         T.TeacherName            AS Name,
         C.CourseID               AS CourseID,
         avg(L.AttendingStudents) over (partition by t.teacherid, c.courseid) AS avgattendingstudents 
  FROM   Teachers T
         join Courses C
           ON C.TeacherID = T.TeacherID
         join Lessons L
           ON L.CourseID = C.CourseID
)
SELECT tmp.name,
       tmp.courseid,
       tmp.AvgAttendingStudents
FROM tmp
where tmp.AvgAttendingStudents = (SELECT max(AvgAttendingStudents)
                                  FROM   tmp tmp2
                                  WHERE  tmp2.id = tmp.id);

See this SQLFiddle for an example.


Note that you can not test the above queries with MySQL because it does not support modern SQL like common table expressions or window functions.

But you can use the SQLFiddle examples to test it with data.

1
On

You may use having clause, which can provide you a way to constraint an agregate function.

Here an example :

    SELECT T.TeacherID              AS ID,
           T.TeacherName            AS Name,
           C.CourseID               AS CourseID,
           avg(L.AttendingStudents) AS AvgAttendingStudents
    FROM   Teachers AS T
           join Courses AS C
             ON C.TeacherID = T.TeacherID
           join Lessons AS L
             ON L.CourseID = C.CourseID
    GROUP  BY T.TeacherID,
              T.TeacherName
              C.CoursesID
    HAVING  avg(L.AttendingStudents) = (SELECT max(AvgAttendingStudents)
                               FROM   Teachers AS tmp2
                               WHERE  tmp2.TeacherID = T.TeacherID);

I just remove your first nested query and change AvgAttendingStudents to avg(L.AttendingStudents) (beacause you can't work with variable onto Having clause) and add selected attributs in Group clause, I don't test but here the way to do the trick.

Don't forget to add your not agregated var selected in group clause.

Here a documentation on having clause.