sql - combining columns from different tables - sql

I have four different tables
class(classID, className)
person(personID, name)
schedule(personID, classID)
enrollment(personID, grade)
What is the easiest way to get each distinct column in one table?
I understand that I would start with enrollment, get the personID and grade, add them to the result table, then use the personID to get the name as well as the classID, and then use the classID to get the className. I just don't know exactly how to do that.

It seems that you need something like this:
SELECT p.name personName, e.grade, c.className
FROM person p
JOIN enrollment e ON e.personId = p.personId
JOIN schedule s ON s.personId = p.personId
JOIN class c ON c.classId = s.classId
WHERE p.personId = 1;

Related

Getting data from unrelated tables

I've got this homework for database 'SCHOOL'
Table Student (StudentID, Name, Surname, etc..)\
Table Application (ApplicationID, StudentID, ClassID)\
Table Class (ClassID, ClassName, TeacherID)\
Table Teacher (TeacherID, Name, Surname, etc..)
There are some other tables and columns in the database but I don't think they're important to this query.
I need to get name of the teachers for whose classes no student signed up. Let's say there is no one signed up for Math class, and I need to get the name of the teacher for that Math class.
SELECT Teacher.Name, Teacher.Surname, Class.ClassName
FROM Teacher
INNER JOIN Class ON Class.TeacherID = Teacher.TeacherID
INNER JOIN Application ON Application.ClassID = Class.ClassID
INNER JOIN Student ON Student.StudentID = Application.StudentID
WHERE Application.PredmetID IS NULL
Help would be appreciated.
I would recommend NOT EXISTS:
select t.name
from class c join
teacher t
on t.teacherid = c.teacherid
where not exists (select 1
from application a
where a.classid = c.classid
);
I need to get name of the teachers for whose classes no student signed up
Select T.name
From Teacher T inner join [Class] C on T.TeacherId = C.TeacherId
Where C.ClassId not in (Select Distinct ClassId from Application)

joining a table on 2 fields

I want to pull a person and their supervisor names from a table. The persons table has the supervisor_id and the person_id. The names table has name_id and a Full Name field. If I join Person ON either supervisor_id or person_id, how do I get the other to display as well?
You need to join twice, one for each relationship you have:
SELECT
-- Persons' columns
P.*,
-- Superviser name columns
SN.*,
-- Person name columns
PN.*
FROM
persons AS P
LEFT JOIN names AS SN ON P.supervisor_id = SN.name_id
LEFT JOIN names AS PN ON P.person_id = PN.name_id
Or you can join with an OR clause, but you won't be able to know which record did you join with unless you check with a CASE.
SELECT
-- Persons' columns
P.*,
-- name columns
N.*,
IsSupervisor = CASE WHEN P.supervisor_id = N.name_id THEN 'Yes' ELSE 'No' END
FROM
persons AS P
LEFT JOIN names AS N ON
P.supervisor_id = N.name_id OR
P.person_id = N.name_id
This last approach will display 2 rows as it will match either one or the other on different occasions, not both with the same persons row (as the first example).
A (self)join is what you need:
select p.*, supervisor=ps.name
from Person p join person ps on p.supervisor_id=ps.id

SQL inline view subquery

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)
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.
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.

Multiple joins onto same table

i have the following tables:
TABLE: teachers:
teacherID
teacherName
TABLE: students:
studentID
studentName
teacherID
advisorID
so, usually, i know i can get a single row per student, with their teachers name using an INNER JOIN.
but in this case - the advisor and tacher - are from the same teachers table. so how can i join onto the teachers table twice - once getting the teacher name, and then again to get the advisor name?
hope this is clear
thanks!
This lists students with the names of their teachers and advisors if any, in alpha order of student, without either (a) the teacher or (b) the advisor having to exist. If you want only where those names exist, change the respective join to an INNER join.
SELECT s.studentname as [Student], t.teachername as [Teacher], a.teachername as [Advisor]
FROM Students s
LEFT JOIN Teachers t ON s.TeacherID = t.TeacherID
LEFT JOIN Teachers a ON s.AdvisorID = a.TeacherID
ORDER BY 1, 2
You can join to the same table more than once, just give it a different alias for each join, and name your fields in a descriptive enough way. Use a left join if there might not be a link, but if a student always has both a teacher and an advisor, a straight join should be fine.
Something like this:
select s.studentname student
, t.teachername teacher
, a.teachername advisor
from students s
join teacher t
on t.teacherID = s.teacherID
join teacher a
on a.teacherID = s.teacherID
Why not try something like the following. Its been a while since I've done SQL so this may not work.
SELECT s.studentName AS Student, t.teacherName AS Teacher, a.teacherName AS Advisor
FROM teachers t, teachers a, students s
WHERE t.teacherID = s.teacherID AND a.teacherID = s.advisorID

LEFT JOIN confusion -- I need to retrieve a student count

I have four tables:
students
classes
teachers
teacher_assignments
classes and teachers has a many-to-many relationship and so teacher_assignments acts as the xref table (with fields teacher_id and class_id).
Each student in students has a class_id (many-to-one -- many students to one class).
I should also mention that teacher_assignments has an active column (BOOL) which indicates whether that assignment is currently active
What I want to do:
I want to retrieve the following:
class_name -- a concat of its level and sub_level, e.g. 3 and A
teacher_names -- the names of the teachers currently assigned to that class
student_count -- a count of the students in each class
At first, I tried retrieving just the class_name and teacher_names, like so:
SELECT
CONCAT(CONVERT(classes.level, CHAR(8)), classes.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names
FROM
teacher_assignments
LEFT JOIN teachers
ON teachers.id = teacher_assignments.teacher_id
AND teacher_assignments.active = TRUE
LEFT JOIN classes
ON classes.id = teacher_assignments.class_id
GROUP BY classes.id
This works fine and outputs:
class_name | teacher_names
--------------------------------------
1A | NULL
2A | John, Sam
3B | Sam, Sarah
(Class 1A has no teachers currently, and so the NULL is expected)
... BUT, now I have no idea how to work the student_count into this.
My question:
How exactly should the students table be joined with the others in the above query so I can produce a student_count column?
Use:
SELECT CONCAT(CONVERT(c.level, CHAR(8)), c.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
COUNT(s.id) AS studentCount
FROM CLASSES c
LEFT JOIN TEACHER_ASSIGNMENTS ta ON ta.class_id = c.id
AND ta.active = TRUE
LEFT JOIN TEACHERS t ON t.id = ta.teacher_id
LEFT JOIN STUDENTS s ON s.class_id = c.id
GROUP BY class_name
Column aliases can be referenced in the GROUP BY when using MySQL, otherwise you'd have to duplicate the logic that produces the class_name column value. This is also the column to GROUP on, as GROUP_CONCAT and COUNT are aggregate functions.
To get zero as the count value, you might need to use:
SELECT CONCAT(CONVERT(c.level, CHAR(8)), c.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
COALESCE(COUNT(s.id), 0) AS studentCount
FROM CLASSES c
LEFT JOIN TEACHER_ASSIGNMENTS ta ON ta.class_id = c.id
AND ta.active = TRUE
LEFT JOIN TEACHERS t ON t.id = ta.teacher_id
LEFT JOIN STUDENTS s ON s.class_id = c.id
GROUP BY class_name
Just thinking off the top of my head...
Join classes and students tables to get the student count...
Instead of doing a left join on classes in your above query, you will do a left join with the result from #1 (essentially an inner join between classes and students tables) that allows you to pull the student count.
I dont think I would use a join but instead would use an inline column select on student like this:
SELECT
CONCAT(CONVERT(classes.level, CHAR(8)), classes.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
( SELECT COUNT(*) FROM students WHERE students.class_id = classes.id ) AS student_count
FROM ...