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
Related
I have two tables, one is departments and the other is employees. The department id is a foreign key in the employees table. The employee table has a name and a flag saying if the person is part-time. I can have zero or more employees in a department. I'm trying to figure out out to get a list of all departments where a department has at least one employee and if it does have at least one employee, that all the employees are part time. I think this has to be some kind of subquery to get this. Here's what I have so far:
SELECT dept.name
,dept.id
,employee.deptid
,count(employee.is_parttime)
FROM employee
,dept
WHERE dept.id = employee.deptid
AND employee.is_parttime = 1
GROUP BY employee.is_parttime
I would really appreciate any help at this point.
You must join (properly) the tables and group by department with a condition in the HAVING clause:
select d.name, d.id, count(e.id) total
from dept d inner join employee e
on d.id = e.deptid
group by d.name, d.id
having total = sum(e.is_parttime)
The inner join returns only departments with at least 1 employee.
The column is_parttime (I guess) is a flag with values 0 or 1 so by summing it the result is the number of employees that are part time in the department and this number is compared to the total number of employees of the department.
As a preliminary aside, I recommend expressing joins with the JOIN keyword, and segregating join conditions from filter conditions. Doing so would make the original query look like so:
select dept.name, dept.id, employee.deptid, count(employee.is_parttime)
from employee
join dept on dept.id = employee.deptid
where employee.is_parttime = 1
group by employee.is_parttime
It doesn't make much practical difference for inner joins, but it does make the structure of the data and the logic of the query a bit clearer. On the other hand, it does make a difference for outer joins, and there is value in consistency.
As for the actual question, yes, one can rewrite the original query using a subquery or an inline view to produce the requested result. (An "inline view" is technically what one should call an embedded query used as a table in the FROM clause, but some people lump these in with subqueries.)
Example using a subquery
select dept.name, dept.id
from dept
where dept.id in (
select deptid
from employee
group by deptid
having count(*) == sum(is_parttime)
)
Example using an inline view
select dept.name, dept.id
from dept
join (
select deptid
from employee
group by deptid
having count(*) == sum(is_parttime)
) pt_dept
on dept.id = pt_dept.deptid
In each case, the subquery / inline view does most of the work. It aggregates employees by department, then filters the groups (HAVING clause) to select only those in which the part-time employee count is the same as the total count. Naturally, departments without any employees will not be represented. If a list of department IDs would suffice for a list of departments, then that's actually all you need. To get the department names too, however, you need to combine that with data from the dept table, as demonstrated in the two example queries.
I am a beginner with SQL syntax. I have 2 tables called STUDENT and EXAMINATION and I am, in 1 line, trying to write a query to print ID, SUBJECT and NUMBER_OF_TIMES. I am not told which column belongs to which table but my guess is that ID belongs to STUDENT and SUBJECT belongs to EXAMINATION. NUMBER_OF_TIMES is just how many times the same student has taken that subject exam.
I tried SELECT STUDENT.ID, EXAMINATION.SUBJECT, NUMBER_OF_TIMES but no luck.
Your working query would look something like this:
SELECT
s.ID,
e.SUBJECT,
COUNT(e.STUDENT_ID) AS cnt
FROM STUDENT s
LEFT JOIN EXAMINATION e
ON s.ID = e.STUDENT_ID
GROUP BY
s.ID,
e.SUBJECT
This answer assumes, really out of necessity, that there exists a join column STUDENT_ID in the exam table, which connects with the student table. We use a left join so as to report all students, even those with a zero count.
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;
Lets say I have the following database model:
And the question is as follows:
List ALL department names and the total number of employees in the department. The total number of employees column should be renamed as "total_emps". Order the list from the department with the least number of employees to the most number of employees. Note: You need to include a department in the list even when the department does not currently have any employee assigned to it.
This was my attempt:
SELECT Department.deptname
(SELECT COUNT(*)
FROM Department
WHERE Department.empno = Employee.empno ) AS total_emps
FROM Department
I'm pretty sure my solution is not correct as it won't include departments with no employees. How do you use a left inner join to solve this problem?
The query as you were trying to write it is:
(table creates modified from shree.pat18's sqlfiddle to this sqlfiddle)
create table department (deptno int, deptname varchar(20));
insert into department values (1, 'a'),(2, 'b'),(3, 'c');
create table employee (empno int, deptno int);
insert into employee values (1,1),(2,1),(3,3);
SELECT d.deptname,
(SELECT COUNT(*)
FROM EMPLOYEE e
WHERE d.deptno = e.deptno ) AS total_emps
FROM DEPARTMENT d
ORDER BY total_emps ASC;
(You were counting from DEPARTMENT instead of EMPLOYEE and comparing empno instead of deptno. And you left out a comma.)
(You were asked for every department's name and employee count so this returns that. In practice we would include a presumably unique deptno if deptname was not unique.)
I'm pretty sure my solution is not correct as it won't include
departments with no employees.
Even your answer's version of the query (with the missing comma added) has an outer select that returns a count for every department no matter what the subselect returns. So I don't know why/how you thought it wouldn't.
If you want to use LEFT (OUTER) JOIN then the DEPARTMENT rows with no employees get extended by NULL. But COUNT of a column only counts non-NULL rows.
SELECT d.deptname, COUNT(e.empno) AS total_emps
FROM DEPARTMENT d
LEFT JOIN EMPLOYEE e
ON d.deptno = e.deptno
GROUP BY d.deptno
ORDER BY total_emps ASC;
(Nb the LEFT JOIN version uses more concepts: LEFT JOIN extending by NULL, GROUP BY, and COUNT's NULL behaviour for non-*.)
First off, it's a left outer join. Now, for your query, you want to join the 2 tables based on deptno, then also group by deptno (or deptname, since that is as likely to be unique) to ensure that any aggregation we do is done for each unique department in the table. Finally, the counting is done with the count function, leading to this query:
select d.deptname, count(e.empno) as total_emps
from department d
left join employee e on d.deptno = e.deptno
group by d.deptname
SQLFiddle
Note that since we want all records from department regardless of whether there are matching records in employee or not, department must appear at the left side of the join. We could have done the same thing using a right outer join by swapping the positions of the 2 tables in the join.
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