Complicated MySQL Query - Do I Use a Right Join? - mysql

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

Related

Having trouble a query and specifically with joins

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

Can't Figure Out how to join tables

SELECT e.ManagerID, count(*) as NumberOfDepartments
From HumanResources.Employee e, Person.Contact c
where e.ContactID = c.ContactID
group by e.ManagerID;
The Goal is to write a report to display the managerid, firstname and lastname of that manager and the number of unique different departments they supervise. and only show the manager that supervises the most departments.
I have to ensure that all employees are currently employed (ie enddate does not contain a date).
The code above is working in showing number the managerID and number of department he runs but whenever I try to put in the first name and last name I have to put them also in the 'group by' clause and that way it makes the whole report going crazy. Please Help.
Database Here
From your schema, seems that the managerID column in Employee is populated with the ID of the manager for that employee. That would explain why when adding firstName and lastName the report goes crazy, because you'd be grouping by the employee's name, not the manager's.
Without seeing the tables content it's hard to tell, but you may have that managers can be recognised by not having managerID populated.
If this is the case, you can write your query like this
select e.EmployeeID, c.firstName, e.lastName, count(distinct edh.DepartmentID)
from Employee e
join Contact c
on e.ContactID = c.ContactID
join Employee e2
on e1.EmployeeID = e2.ManagerID
join EmployeeDepartmentHistory edh
on e2.EmployeeID = edh.EmployeeID
where e.ManagerID is null and edh.EndDate is null
group by e.EmployeeID, c.firstName, e.lastName
The first instance of Employee table is the managers (because you set where e.ManagerID is null), the join with Contact gets you the managers' names, the second instance of Employee gets you all the people managed by each manager, and the join with EmployeeDepartmentHistory gets you their department (which you count on) and their EndDate, that has to be null to ensure you that they're currenty employed.
Edit
Please note the way I wrote the joins; writing them as comma separated tables names in your from clause with the join condition in your where is a bad habit that should be kicked, because it makes reading, maintaining and changing them to outer joins much harder. That's why join was introduced in SQL language back in 1992.
In MSSQL:
SELECT e.ManagerID, e.FirstName, e.LastName, COUNT(*) AS NumberOfDepartments FROM HumanResources.Employee e
INNER JOIN Person.Contact c ON e.ContactID=c.ContactID
GROUP BY e.ManagerID, e.FirstName, e.LastName
If you need it in MySql, change ON to WHERE pattern and INNER JOIN to JOIN

MySQL query issue

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.

Using a Combination of INNER and FULL OUTER Joins to Get Desired Data Set

I’ve got 5 Tables:
Patients Table 'PAT'
Patient Orders 'ORD'
Patient Comments 'Comm'
Patient Antigens 'Ant'
Patient MRNs 'MRN'
Information in the following four tables:
Pat has all of our patients information
Ord only has orders from the last four years, joined to the Pat table on Patient ID
Comm has all patient comments, joined on the Pat table on Patient ID
Ant has all antigens entered on patients, joined on the Pat table on Patient ID
MRN has all medical record numbers of patients, joined on the Pat table on Patient ID
What I’m trying to do it make sure I have all patients that appear in the Ord, Comm, Ant, and MRN tables and the join it to the Pat table to get information like first name, last name, sex, DOB...
So I’m trying to figure out how best to join these tables together to make sure I don’t include patients that don’t appear in the Ord, Comm, Ant, or MRN tables.
If I join the tables to my main patient table with left outer joins, I'll get all patients regardless if they have an order, comment, antigen, or MRN.
Select * from Pat
Left Outer Join Ord on Pat.X=Ord.X
Left Outer Join Comm on Pat.X=Comm.X
Left Outer Join Ant on Pat.X=Ant.X
Left Outer Join MRN on Pat.X=MRN.X
I've attempted to make a visual diagram of what I'm looking for:
My ideal query would return all Patients except for 4 and 10 because they do not have any orders, antigens, comments, or a MRN.
I was thinking I could perform FULL OUTER JOINS on the ORD, ANT, COMM, and MRN tables and then take those results and and join it to the PAT table.
Select *
FROM P50DATA.AGABFL1 ANT
FULL OUTER JOIN P50DATA.ORDMSTL1 ORD ON ANT.AGACCT=ORD.OPACCT
FULL OUTER JOIN P50DATA.DCMTRNL5 COMM ON ANT.AGACCT=COMM.DCACCT
FULL OUTER JOIN P50DATA.HOSPIDL1 HOS ON ANT.AGACCT=HOS.APACCT
But I don't know how to then take this data set and marry it to the Pat table so I could then get my patient information.
Thoughts?
This doesn't have to be too complicated.
ORD, ANT, COMM, and MRN don't have to join to each other, because you don't need the records in any one of them to match any other one. You just need to know whether they have records for a given patient or not.
There are many ways to construct such a query; here's one:
SELECT *
FROM PAT
WHERE EXISTS (
SELECT 1
FROM ORD
WHERE ORD.X = PAT.X
)
OR EXISTS (
SELECT 1
FROM ANT
WHERE ANT.X = PAT.X
)
OR EXISTS (
SELECT 1
FROM COMM
WHERE COMM.X = PAT.X
)
OR EXISTS (
SELECT 1
FROM MRN
WHERE MRN.X = PAT.X
)
Because you are querying the Patient table without joining it to anything, you don't have to worry about duplicate records, and because you're just checking existence for ORD, ANT, COMM, and MRN, you don't have to worry about exactly how many records each of them has for a given patient.

SQL join to return results only if all rows match

this is my first time on the site so please be gentle. I have tried to find my answer here, but nothing seems to fit the bill and after reading up on different types of joins, I'm still none the wiser.
I have the following tables:
employees (employee_id, name)
assessments (assessment_id, name, pass_score)
authorizations (authorization_id, name)
Employees take assessments which is logged in:
assessments_taken (assessment_taken_id, employee_id, assessment_id, score)
An authorization is composed of multiple individual assessments given by:
authorization_requirements (auth_req_id, authorization_id, assessment_id)
where authorization_id & assessment_id are unique constraints to prevent duplicates of the pair.
The query I am trying to design is something along the lines of:
SELECT employees.name, authorization.name ...
I only want to return employees and authorizations where an employee has passed all assessments for that authorization given in authorization_requirements.
Can anyone help me find the query/sub-queries I am looking for?
The below image may help you somewhat in learning SQL joins - it even gives you some of the SQL itself!. As for the authorization_requirements it sounds like you'll need to be using some WHERE clause along with the join.
Hope this helps, and any clarification, please feel free to ask!
Have a look at this http://sqlfiddle.com/#!2/ac7887/2
I think this query will do it for you. It relies on there only being one entry in the Assessments Taken per employee per assessment. If employees can take the assessments multiple times and all results are recorded in this table, there's a second query that should work for you.
SELECT
Employees.name Employee,
authorizations.name Authorization
FROM
employees
CROSS JOIN authorizations
INNER JOIN authorization_requirements ON authorizations.authorization_id = authorization_requirements.authorization_id
INNER JOIN assessments ON authorization_requirements.assessment_id = assessments.assessment_id
LEFT JOIN assessments_taken ON employees.employee_id = assessments_taken.employee_id AND assessments.assessment_id = assessments_taken.assessment_id AND assessments.pass_score <= assessments_taken.score
GROUP BY
Employees.name,
authorizations.name
HAVING
COUNT(assessments.assessment_id) = COUNT(assessments_taken.assessment_taken_id)
If multiple entries in assessments_taken:
SELECT
Employees.name Employee,
authorizations.name Authorization
FROM
employees
CROSS JOIN authorizations
INNER JOIN authorization_requirements ON authorizations.authorization_id = authorization_requirements.authorization_id
INNER JOIN assessments ON authorization_requirements.assessment_id = assessments.assessment_id
LEFT JOIN (SELECT
assessments_taken.employee_id,
assessments_taken.assessment_id,
MAX(Score) best_score
FROM
assessments_taken
GROUP BY
assessments_taken.employee_id,
assessments_taken.assessment_id
) best_assessments_taken ON employees.employee_id = best_assessments_taken.employee_id AND assessments.assessment_id = best_assessments_taken.assessment_id AND assessments.pass_score <= best_assessments_taken.best_score
GROUP BY
Employees.name,
authorizations.name
HAVING
COUNT(assessments.assessment_id) = COUNT(best_assessments_taken.assessment_id)
These queries get every employee and authorization and then ensure that the employee has got an entry in assessments_taken which exceeds the required score for every assessment specified in authorization_requirements
If CROSS JOIN is not supported you might need to put those two tables in a sub query and then change the join conditions to reference the columns from the sub query instead:
SELECT
employee_authorizations.employee_name Employee,
employee_authorizations.authorization_name Authorization
FROM
(SELECT
employees.employee_id,
employees.name employee_name,
authorizations.authorization_id,
authorizations.name authorization_name
FROM
employees,
authorizations) employee_authorizations
INNER JOIN authorization_requirements ON employee_authorizations.authorization_id = authorization_requirements.authorization_id
INNER JOIN assessments ON authorization_requirements.assessment_id = assessments.assessment_id
LEFT JOIN (SELECT
assessments_taken.employee_id,
assessments_taken.assessment_id,
MAX(Score) best_score
FROM
assessments_taken
GROUP BY
assessments_taken.employee_id,
assessments_taken.assessment_id
) best_assessments_taken ON employee_authorizations.employee_id = best_assessments_taken.employee_id AND assessments.assessment_id = best_assessments_taken.assessment_id AND assessments.pass_score <= best_assessments_taken.best_score
GROUP BY
employee_authorizations.employee_name,
employee_authorizations.authorization_name
HAVING
COUNT(assessments.assessment_id) = COUNT(best_assessments_taken.assessment_id)