I have been working on this query for a table lookup for hours and I am finally calling in for help to SO.
There are three tables:
computer (assetTag for ID, other information about a computer)
student (studentID for ID, other student information)
assignment (studentID, assetTag, issueDate)
These are obviously linked as a many to many using assignment like a junction table:
Student <-> assignment <-> computer
I put together a query to show a list of student names, and the last computer assignment they had. The query works great except for one problem I can't wrap my head around. If studentA is assigned computer 10000 and then turns it in, and then that computer is assigned to studentB until the end of the year when they turned it in, looking up that asset shows both students as the rightful owners of the machine. I need it to only show the latest student.
Current query:
SELECT SQL_CALC_FOUND_ROWS s.studentID, s.lName, s.fName, s.uName, s.grade, c.assetTag, c.location, c.`status` FROM student s
INNER JOIN
(
SELECT studentID, MAX(issueDate) MaxDate
FROM assignment GROUP BY studentID
) MaxDates ON s.studentID = MaxDates.studentID AND s.active = 1
INNER JOIN assignment a ON MaxDates.studentID = a.studentID AND MaxDates.MaxDate = a.issueDate AND a.loaner = 0
INNER JOIN computer c ON a.assetTag = c.assetTag
For the most part previous owners of a machine are inactive (s.active = 0) so there aren't very many duplicates but there shouldn't be any duplicate assetTags.
I wish I could aggregate assetTags based off the last assignment EVER.
Any help would be greatly appreciated since I need to get this done pretty fast.
EDIT:
It may help if I explain that there are MANY students to MANY computers and MANY computers to MANY students.
Throughout the year a student may change computers repeatedly and a computer may change to different students repeatedly.
Your subquery could look like this:
SELECT studentID, issueDate MaxDate
FROM assignment
GROUP BY studentID
HAVING issueDate = max(issueDate)
This will return a list of studentIDs with each student's most recent assignment.
As an alternative, you can replace your entire mess of a query with:
select SQL_CALC_FOUND_ROWS s.studentID, s.lName, s.fName, s.uName, s.grade, c.assetTag, c.location, c.`status`, a.issueDate
from assignment a
inner join student s on a.studentID = s.studentID
inner join computer c on a.assetTag = c.assetTag
group by a.assetTag
having a.issueDate = max(a.issueDate)
Updated since having doesn't work like that:
select SQL_CALC_FOUND_ROWS s.studentID, s.lName, s.fName, s.uName, s.grade, c.assetTag, c.location, c.`status`, a.issueDate
from assignment a
inner join student s on a.studentID = s.studentID
inner join computer c on a.assetTag = c.assetTag
where a.issueDate = (select max(issueDate) from assignment where assetTag=a.assetTag)
group by a.assetTag
Related
The code below is completely wrong and does not work at all. Im basically trying to look through my tables and compile a list of DeptName and the total student number for a department where a department has more than 40 students.
Im confused about joins in general and if someone could explain and show where im going wrong. im sure there is also other problems so any help with them would help
So basically one department is connected to one module, and a student is enrolled in a module. A student cannot take a module outside of their department. So each student should have one module that connects to one department
All of the ID fields in other tables are foreign keys as you can guess and changing the tables is not what I want to do here I just want to do this query as this stands
Relevant tables columns
Table Department DeptID, DeptName, Faculty, Address
Table Modules ModuleID, ModuleName, DeptID, Programme
Table Students StudentID,StudentName,DoB,Address,StudyType,`
Table Enrolments EID,StudentID,ModuleID,Semester,Year
SELECT Department.DeptName, COUNT(Student.StudentID) AS 'No of Students' FROM Department LEFT JOIN Module ON Department.DeptID= Module.DeptID LEFT JOIN Enrolment ON Module.ModuleID= Enrolment.StudentID LEFT JOIN Student.StudentID
GROUP BY(Department.DeptID)
HAVING COUNT(Student.StudentID)>=40
I have not included every table here as there are quite a lot.
But unless i've got this completely wrong you don't need to access a ModuleID in a staff table for the module they teach or something not relevant to this at all. As no student or Dept details are in there.
If that is the case i will fix it very quickly.
SELECT Department.DeptName, COUNT(Student.StudentID) AS 'No of Students'
FROM Department
LEFT JOIN Module
ON Department.DeptID= Module.DeptID
LEFT JOIN Enrolment
-- problem #1:
ON Module.ModuleID= Enrolment.StudentID
-- problem #2:
LEFT JOIN Student.StudentID
-- problem #3:
GROUP BY(Department.DeptID)
HAVING COUNT(Student.StudentID)>=40
You're joining these two tables using the wrong field. Generally when the modeling is done correctly, you should use USING instead of ON for joins
The right side of any JOIN operator has to be a table, not a column.
You have to group by every column in the select clause that is not part of an aggregate function like COUNT. I recommend that you select the DeptID instead of the name, then use the result of this query to look up the name in a subsequent select.
Note : Following code is untested.
WITH bigDepts AS (
SELECT DeptId, COUNT(StudentID) AS StudentCount
FROM Department
JOIN Module
USING ( DeptID )
JOIN Enrolment
USING ( ModuleID )
JOIN Student
USING ( StudentID )
GROUP BY DeptID
HAVING COUNT(StudentID)>=40
)
SELECT DeptID, DeptName, StudentCount
FROM Department
JOIN bigDepts
USING ( DeptID )
Instead of left join you need to use inner join since you need to select related rows only from those three tables.
Groupy by and having clause seems fine. Since you need departments with more than 40 students instead of >= please use COUNT(e.StudentID)>40
SELECT d.DeptName, COUNT(e.StudentID) AS 'No of Students' FROM Department d INNER JOIN Module m ON d.DeptID= m.DeptID inner JOIN Enrolment e ON m.ModuleID= e.StudentID LEFT JOIN Student.StudentID
GROUP BY(d.DeptName)
HAVING COUNT(e.StudentID)>40
So your join clause was a bit iffy to students as you wrote it, and presumably these should all be inner joins.
I've reformatted your query using aliases to make it easier to read.
Since you're counting the number of rows per DeptName you can simply do count(*), likewise in your having you are after counts greater than 40 only. Without seeing your schemas and data it's not possible to know if you might have duplicate Students, if that's the case and you want distinct students count can amend to count(distinct s.studentId)
select d.DeptName, Count(*) as 'No of Students'
from Department d
join Module m on m.DeptId=d.DeptId
join Enrolment e on e.StudentId=m.ModuleId
join Students s on s.StudentId=e.studentId
group by(d.DeptName)
having Count(*)>40
Also, looking at your join conditions, is the Enrolement table relevant?
select d.DeptName, Count(*) as 'No of Students'
from Department d
join Module m on m.DeptId=d.DeptId
join Students s on s.StudentId=m.moduleId
group by(d.DeptName)
having Count(*)>40
I'm learnin SQL from a book and i'm trying to do some exercices on join queries.The only problem that i'm facing is that all of my join queries are not working while they seem well
students(student_id,student_names,student_age)
courses_students(course_id,student_id)
courses(course_id,course_schedule,course_room,teacher_id)
teachers(teacher_id,teacher_names)
The query is "which courses have more than 5 students enrolled?"
Here is what i've done :
SELECT course_name,
count
(SELECT count(*)
FROM courses) AS COUNT
FROM students,
courses,
courses_students
WHERE students.student_id=courses_students.student_id,
courses.course_id=courses_students.course_id
AND COUNT > 5
And the other one is what are the names of students enrolled in at least 2 courses scheduled for the same hours
My query :
SELECT student_name,
schedule
FROM students,
courses,
courses_students
WHERE students.student_id=courses_students.student_id,
courses.course_id=courses_students.course_id
AND COUNT > 2
In this inner query:
(SELECT count(*)
FROM courses) AS COUNT
you need to narrow down what is included in the COUNT. As it is, it is selecting all items in the courses table. The inner query does not know about the restrictions in the outer query. Try adding a where clause in this inner query. You might need to add table aliases to uniquely refer to the correct courses table, so there is no ambiguity whether it is referring to the courses table in the inner query or the outer query.
And, as noted in other answers, this is not the best way to structure joins.
In MySQL you are required to define joins explicitly. Unlike Oracle it can't handle joins with sign of =.
SELECT course_name,
count
(SELECT count(*)
FROM courses) AS COUNT
FROM students
INNER JOIN courses on courses.course_id=courses_students.course_id
INNER JOIN courses_students on students.student_id=courses_students.student_id
WHERE COUNT(*) > 2
You need to aggregage by course and then assert the number of students:
SELECT
c.course_name,
COUNT(*) AS cnt
FROM courses c
INNER JOIN courses_students cs
ON c.course_id = cs.course_id
INNER JOIN students s
ON cs.student_id = s.student_id
GROUP BY
c.course_name
HAVING
COUNT(*) > 5;
stackoverflow is actually not a site to do homework, but as you already have given a try to solve the task, here is a solution for question number one:
SELECT cs.course_id
FROM courses_students cs
INNER JOIN students s
ON cs.course_id = s.course_id
GROUP BY cs.course_id
HAVING count(*) > 5
Read about the GROUP BY and HAVING clause - nice way to solve some problems.
Question number 2 could be solved like this:
SELECT student_names
FROM students s
INNER JOIN courses_students cs
ON cs.student_id = s.student_id
INNER JOIN (
SELECT course_id
FROM courses c
GROUP BY course_schedule
HAVING count(*) > 1
) sub
ON sub.course_id = cs.course_id
The INNER JOIN with the subquery is selecting courses which are scheduled at the same time (having the same course_schedule).
As the other tables are "connected" with INNER JOINs, we will finally just have the subset of students which are participating one of those courses.
I have the two tables STUDENT and TaskEffort.
Many students have worked on same tasks. For a particular task the name of the students and effort should be considered.
The STUDENT table contains studentid, firstName and lastName.
The TaskEffort table contains taskid, studentid and Effort
I need to display the taskid, first name, last name, effort, for those who worked on a particular task.
This is one of the queries I tried, but this is not working.
SELECT t.id, s.firstname, s.lastname, t.effort
FROM taskeffort t
LEFT OUTER JOIN student s ON t.id = 4 AND s.studentid = t.studentid
Thanks in advance.
First the design is not accurate.
You said that "Many students must have worked on same task" but your design is
1-many students. It means that each task has only one student. You need to change the design to support you requirements.
this relation is many-to-many. Student can be assigned to many tasks and tasks may have been assigned to many students.
Add a third table called taskAssignments with columns: student_id,task_id and remove student_id column from TaskEffort.
Then run this query:
SELECT t.id, s.firstname, s.lastname, t.effort
FROM TaskEffort t
LEFT JOIN taskAssignments ta ON t.id=ta.task_id
LEFT JOIN student s ON s.studentid = ta.studentid
WHERE t.id = 4
SELECT t.id, s.firstname,s.lastname, t.effort
FROM taskeffort t
LEFT OUTER JOIN student s ON s.studentid = t.studentid
WHERE t.id = 4;
This is my query:
SELECT count(*) as total, dp.name,dp.id,dp.description, dp.avatar
FROM `doctors` d
right join departments dp on d.department_id = dp.id
group by d.department_id
I have to tables: doctors and departments. I want to extract the total number of doctors from each department. This query works fine, it returns me all of the deparments, which have a doctors, but not which does not have. Somehow I want to show all of the departements and a total, which represents the doctors whose belong to a department. How can i do that ?
This is the doctor table:
and this is the departments table
You can give this a try:
SELECT
(SELECT count(*) FROM doctors d WHERE d.department_id = dp.id) AS total,
dp.*
FROM departments AS dp
And if you want to use JOIN then try this:
SELECT
COUNT(d.department_id) AS total,
dp.*
FROM departments AS dp
LEFT JOIN doctors AS d ON dp.id = d.department_id
GROUP BY dp.id
SELECT (SELECT count(*) FROM doctors AS d WHERE d.id = dp.id) as total, dp.name,dp.id,dp.description, dp.avatar
FROM departments dp
there are multiple ways of doing this ofcourse, I would do it like this
i dont think you need to join you just need a count of all the doctors and you dont do anything with the rest of the information
In my couple of decades working with SQL I've never used right joins. I've always found the LEFT join easier to read. I also try and return the columns in order of the highest to lowest level of detail and finish with sum's and counts. It reads a lot better.
Try this:
SELECT dp.id,dp.name,dp.description, dp.avatar,count(*) as total
FROM departments dp
LEFT JOIN doctors d
on d.department_id = dp.id
GROUP BY dp.id,dp.name,dp.description, dp.avatar
You must always group by every column within your select clause except those that are an aggregation (e.g. sum,count) or a constant.
OK,
I have a table with people (id, name, address, etc.). Which has a one to many relationship with a table employees (id, person_id, salary, department_id, etc.). And the employees all belong to (many to one relationship) different departments (id, title, location, year_id) which are year-specific.
I want to write a query to find the employees who worked in 2013 and exclude those who continue to work for the company in 2014. Basically I want those who worked for the company last year but don't anymore. I made a feeble attempt below.
SELECT * FROM people
JOIN employees ON people.id=employees.person_id
RIGHT JOIN departments ON employees.department_id=departments.id
AND departments.year_id='2013'
WHERE departments.year_id<>'2014'
Any help would be appreciated.
You can use not exists check for those employees who discontinued work in 2014
SELECT * FROM people
JOIN employees e ON people.id=e.person_id
RIGHT JOIN departments ON e.department_id=departments.id
WHERE departments.year_id='2013'
AND NOT EXISTS
(SELECT 1
FROM departments
JOIN employees ee ON ee.department_id=departments.id
WHERE departments.year_id = '2014'
AND e.id=ee.id
)
Or not in() solution but its not recommended due to a dependent sub query
SELECT * FROM people
JOIN employees e ON people.id=e.person_id
RIGHT JOIN departments ON e.department_id=departments.id
WHERE departments.year_id='2013'
AND e.id NOT IN
(SELECT ee.id
FROM departments
JOIN employees ee ON ee.department_id=departments.id
WHERE departments.year_id = '2014'
)
I'm unsure as to why you have a one to many on people to employees or how this functionality is applied within your system, but if I understand it correctly, this should work.
M Khalid Junaid's answer will likely work however I despise using sub-queries as they do not tend to scale well. The below should work while returning only 1 row per employee (no groups) in addition it only has 1 left join and no sub-queries.
SELECT * FROM people
JOIN employees ON people.id=employees.person_id
JOIN departments as dept_start ON employees.department_id=departments.id && departments.year_id = 2013
LEFT JOIN departments as dept_end ON employees.department_id=departments.id && departments.year_id = 2014
WHERE dept_start.id IS NOT NULL && dept_end.id IS NULL