Find records for students missing a class - sql

I have two tables, one that flags a user as having passed a course, and a list of courses per job code. I'm trying to query to return a record for all users that are missing classes.
Here are the tables:
Attended
--------
empid jobcode classcode grade
555 1 100 A
555 1 101 A
444 2 200 A
JobClassCode
--------
jobcode classcode
1 100
1 101
1 102
2 100
2 200
3 300
3 301
I started with this query to find classes with a missing user:
select * from attended at
right outer join jobcodeclass jc on at.jobcode = jc.jobcode and at.classcode = jc.classcode
I then tried to take that to build a correlated subquery, but I don't see a way to return both the user ID and missing course ID:
select * from jobcodeclass oq where classcode in (select jc.classcode from attended at
right outer join jobcodeclass jc on at.jobcode = jc.jobcode
and at.classcode = jc.classcode and jc.jobcode = oq.jobcode
and oq.classcode = jc.classcode and empid is null)

Generate all the possible classes that each employee needs by joining on the jobcode. The see which ones the student attended:
select ej.empid, ej.jobcode, jss.classcode
from (select distinct empid, jobcode from attended) ej join
JobClassCode jcc
on jcc.jobcode = ej.jobcode left join
attended a
on a.empid= e.empid and a.jobcode = ej.jobcode and
a.classcode = jcc.classcode
where a.empid is null;
If you just need the employees, use select distinct ej.empid.

Related

Need Simple SQL direction

I'm trying to write a Ado SQL statement for my Access table and I'm getting the wrong results.
Employee Table
ID Name DriverID
1 Alex 1
2 Tom 2
3 Trevor 3
4 PHIL 0
5 Gina 4
Vehicle Table
ID PLATE EMPLOYEEID INSERVICE
1 123XYZ 1 N
2 456GFR 2 Y
3 TFV4FG 3 Y
4 F6GK7D 4 Y
5 GEY7GH 1 Y
I want result of All employes and to display the Vehcicle info if they are assigned to it.
Result should be
Name Plate
Alex GEY7GH
Tom 456GFR
Trevor TFV4FG
PHIL
Gina F6GK7D
SELECT Employee.ID, Employee.FirstName, Vehicles.Plate, Vehicles.InService
FROM Employee LEFT JOIN Vehicles ON Employee.ID = Vehicles.DriverID
WHERE (((Vehicles.InService)=True));
Does not display PHIL who is not assigned to a vehicle.
Just add the condition inside the join, making sure to use parentheses to avoid problems when joining with constants or anything but simple equals:
SELECT Employee.ID, Employee.FirstName, Vehicles.Plate, Vehicles.InService
FROM Employee LEFT JOIN Vehicles ON (Employee.ID = Vehicles.DriverID AND Vehicles.InService = True)
From the above tables, it looks like the DriverID Column in employee table aligns with the EmployeeID column in the vehicles table and the issue is the on clause in the join.
SELECT
Employee.ID
,Employee.FirstName
,Vehicles.Plate
,Vehicles.InService
FROM
Employee
LEFT JOIN Vehicles ON Employee.DRIVERID = Vehicles.EMPLOYEEID
WHERE
(((Vehicles.InService)=True));
Well, in a normal database, you would move the WHERE condition to the ON clause. But I don't think that MS Access supports this:
SELECT e.ID, e.FirstName, v.Plate, v.InService
FROM Employee as e LEFT JOIN
Vehicles as v
ON e.ID = v.DriverID AND v.InService = True;
An alternative is a subquery:
SELECT e.ID, e.FirstName, v.Plate, v.InService
FROM Employee as e LEFT JOIN
(SELECT v.*
FROM Vehicles as v
WHERE v.InService = True
) as v
ON e.ID = v.DriverID ;

SQL: Unable to find a join or union to produce the following table

A Pupil table with { ID, LastName}
a Subject Table with {ID, SubjectName}
and a Report Table with {ID, PupilID, SubjectID, Grade}
There is a one-to-many relationship between Pupil and Report Tables, and Subject and Report Tables.
I want to generate a table like this for say subjectID = 1
Pupil.ID Pupil.LastName SubjectID Grade
1 --------------Smith ---------- 1 ------------B
2 --------------Jones ---------- 1 ------------NULL
3 -------------Weston ----------1 ------------NULL
4 -------------Knightly ---------1 -----------A
The problem is that the Report table would contain just 2 entries for subject 1:
PupilID SubjectID Grade
----1------- 1 ----------- B
----4------- 1 ----------- A
Left joins don't seem to work since there are only 2 entries in the report table for subject 1
SAMPLE DATA
{Pupil Table}
ID LastName
1 ...Smith
2 ...Jones
3 ...Weston
4 ...Knightly
{Subject Table}
ID SubjectName
1 ....Maths
2 ....Physics
3 ....Chemistry
{Report Table}
ID PupilID SubjectID Grade
1 .......1 ..........1 ..........B
2 .......4 ..........1 ..........A
When I do a search on SubjectID = 1 I want the table:
Pupil.ID .......Pupil.LastName ........SubjectID ...........Grade
1 --------------Smith ---------- 1 ------------B
2 --------------Jones ---------- 1 ------------NULL
3 -------------Weston ----------1 ------------NULL
4 -------------Knightly ---------1 -----------A
Access doesn't do subqueries very easily, so everything gets crammed into the FROM clause with a series of wrapped parentheses. Based on your sample data and my fighting Access to stop being unnecessarily difficult, I came up with this:
SELECT ps.Pupil_ID, ps.LastName, ps.Subject_ID, r.Grade
FROM (SELECT * FROM (SELECT ID AS Pupil_ID, LastName FROM Pupil) p,
(SELECT DISTINCT ID AS Subject_ID FROM Subject)) ps
LEFT JOIN REPORT r ON r.PupilID = ps.Pupil_ID AND r.SubjectID = ps.Subject_ID
ORDER BY Pupil_ID, Subject_ID;
The subquery "ps" is a cartesian join of the Pupil and Subject table views that I specified. At this point, your query would look like this:
(LastName column not shown for clarity)
StudentID|SubjectID
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
Now, using that Cartesian join subquery (pupilstudent -> ps), I use a LEFT JOIN to assign the Report table to each unique student's ID and subject ID. Therefore, if a student did not take a particular class, there will be a NULL value in the final result.
I tested this in Access using your sample data and it works on my machine.
Also as a note, it is poor practice to have a field called just ID in each table (e.g. in the Pupil table, ID becomes PupilID). This makes it much easier to use, and it self documents.
Cross join pupil and subject tables and left join result to report table
What you need is a cross join:
SELECT Pupil.ID, Pupil.LastName, SubjectID, Grade FROM
Pupil, Subject LEFT JOIN Report ON Subject.ID=Report.SubjectID
WHERE Subject.ID=1
To combine every pupil with every (or with a particular) subject, use cross join; Then use left join to get the corresponding grades:
select *
from pupil p cross join (select * from subject where id = 1) s
left join report on subjectId = s.id and pupilId = p.id

How to effeciently SELECT Nested dependency Tables using JOIN in SQL Server

I'm having One Parent Table "Employee", the Employee Information is stored in 3 Children Tables and each children table has one children Table. Consider the following Tables
Table : Employee (Level #1)
EmpId IsActive
__________________
1 1
2 1
3 1
4 0
5 0
6 1
Table : EmployeeEmail (Level #2)
EmpEmailId EmpId EmailId
______________________________
1 1 1
2 4 3
3 6 4
Table : EmailAddress (Level #3)
EmailId Email
____________________________
1 one#gmail.com
2 two#gmail.com
3 three#gmail.com
4 four#gmail.com
Table : EmployeePhone (Level #2)
EmpPhoneId EmpId PhoneId
______________________________
1 1 1
2 2 2
3 5 4
Table : PhoneNumber (Level #3)
PhoneId PhoneNumber
_______________________
1 9912345671
2 9912345672
3 9912345673
4 9912345674
Now I need to Select the Active Employee Records (Full Information), if the employee has phone number then it should come otherwise it should be NULL, I need the same for Email too.
Kindly assist me how to Join this structure and fetch the records efficiently ?
Try this query once it may help you.
select EMP.EmpId, EMP_ADD.Email,PH_NBR.PhoneNumber
from Employee EMP
LEFT JOIN EmployeeEmail EMP_EM ON EMP.EMP_ID=EmpId
LEFT JOIN EmailAddress EMP_ADD ON EMP_ADD.EmailId=EMP_EM.EmpEmailId
LEFT JOIN EmployeePhone EMP_PH ON EMP_PH.EmpId=EMP.EmpId
LEFT JOIN PhoneNumber PH_NBR ON PH_NBR.PhoneId=EMP_PH.PhoneId
WHERE EMP.IsActive=1
Note: Here i assume Active as 1 and Inactive as 0.in case if it is reverse then change where EMP.IsActive=0
(OR) Try this.here i'm trying to the phone and email data separately then joining them with emp_id
;with email as
(
select EMP.EmpId, EMP_ADD.Email
from Employee EMP
LEFT JOIN EmployeeEmail EMP_EM ON EMP.EMP_ID=EmpId
LEFT JOIN EmailAddress EMP_ADD ON EMP_ADD.EmailId=EMP_EM.EmpEmailId
WHERE EMP.IsActive=1
)
,Phone as
(
select EMP.EmpId,PH_NBR.PhoneNumber
from Employee EMP
LEFT JOIN EmployeePhone EMP_PH ON EMP_PH.EmpId=EMP.EmpId
LEFT JOIN PhoneNumber PH_NBR ON PH_NBR.PhoneId=EMP_PH.PhoneId
WHERE EMP.IsActive=1
)
select * from email e
left join Phone p on p.EmpId=e.EmpId
Alternative form:
only the three tables that you actually need are in the main query
the junction tables are used to glue them together via ON EXISTS(...):
SELECT e.empid, em.email, pn.phonenumber
FROM employee e
LEFT JOIN emailaddress em
ON EXISTS ( -- junctiontable
SELECT * FROM employeeemail ee
WHERE ee.empid = e.emp_id
AND ee.empemailid = em.emailid
)
LEFT JOIN phonenumber pn
ON EXISTS( -- junctiontable
SELECT * FROM employeephone ep
WHERE ep.empid = e.empid
AND ep.phoneid = pn.phoneid
)
WHERE e.isactive=1
;

Oracle, LEFT OUTER JOIN not returning all rows from left table, instead behaving like INNER JOIN

I'm doing a left outer join and only getting back matching rows like it was an inner join.
To simplify the data, my first table(ROW_SEG), or left table looks something like this:
ASN | DEPT NO
-----------------------
85 | 836
86 | null
87 | null
My second table(RF_MERCHANT_ORG) has DEPT_NAME, and some other things which i want to get when i have a dept number.
DEPT NO | DEPT_NAME
-----------------------
836 | some dept name 1
837 | some dept name 2
838 | some dept name 3
In this case after my join i'd only get 1 row, for ASN 85 that had a DEPT NO.
...omitting a bunch of SQL for simplicity
, ROW_SEG AS (
SELECT *
FROM VE_SI_EC_OI
WHERE ROW_NUM BETWEEN 1 AND 1000 -- screen pagination, hardcoding values
)
-- ROW_SEG has a count of 1000 now
, RFS_JOIN AS (
SELECT ROW_SEG.*
,MO.BYR_NO
,MO.BYR_NAME
,MO.DEPT_NAME
FROM ROW_SEG
LEFT OUTER JOIN RF_MERCHANT_ORG MO
ON ROW_SEG.DEPT_NO = MO.DEPT_NO
WHERE MO.ORG_NO = 100
)
SELECT * FROM RFS_JOIN; -- returns less than 1000
I only get back the number of rows equal to the number of rows that have dept nos. So in my little data example above i would only get 1 row for ASN 85, but i want all rows with BYR_NO, BYR_NAME, AND DEPT_NAME populated on rows where i had a DEPT_NO, and if not, then empty/null columns.
If ORG_NO is within the RF_MERCHANT_ORG table (using aliases consistently would help there) then acting like an inner join would then would be the correct result for the SQL being used.
The join should be this to make it act like a proper left join:
LEFT OUTER JOIN RF_MERCHANT_ORG MO ON ROW_SEG.DEPT_NO = MO.DEPT_NO AND MO.ORG_NO = 100
If ORG_NO is in RF_MERCHANGE_ORG, then that is likely to be the cause... the where condition is limiting the result set.

In SQL how do I write a query to return 1 record from a 1 to many relationship?

Let's say I have a Person table and a Purchases table with a 1 to many relationship. I want to run a single query that returns this person and just their latest purchase. This seems easy but I just can't seem to get it.
select p.*, pp.*
from Person p
left outer join (
select PersonID, max(PurchaseDate) as MaxPurchaseDate
from Purchase
group by PersonID
) ppm
left outer join Purchase pp on ppm.PersonID = pp.PersonID
and ppm.MaxPurchaseDate = pp.PurchaseDate
where p.PersonID = 42
This query will also show the latest purchase for all users if you remove the WHERE clause.
Assuming you have something like a PurchaseDate column and want a particular person (SQL Server):
SELECT TOP 1 P.Name, P.PersonID, C.PurchaseDescription FROM Persons AS P
INNER JOIN Purchases AS C ON C.PersonID = P.PersonID
WHERE P.PersonID = #PersonID
ORDER BY C.PurchaseDate DESC
Many Databases preform the "Limit or Top" command in different ways. Here is a reference http://troels.arvin.dk/db/rdbms/#select-limit and below are a few samples
If using SQL Server
SELECT TOP 1
*
FROM Person p
INNER JOIN Purchases pc on pc.PersonID = P.PersonID
Order BY pc.PurchaseDate DESC
Should work on MySQL
SELECT
*
FROM Person p
INNER JOIN Purchases pc on pc.PersonID = P.PersonID
Order BY pc.PurchaseDate DESC
LIMIT 1
Strictly off the top of my head!...If it's only one record then...
SELECT TOP 1 *
FROM Person p
INNER JOIN Purchases pu
ON p.ID = p.PersonId
ORDER BY pu.OrderDate
WHERE p.ID = *thePersonYouWant*
otherwise...
SELECT TOP 1 *
FROM Person p
INNER JOIN
(
SELECT TOP 1 pu.ID
FROM Purchases pu
ON pu.PersonID = p.Id
ORDER BY pu.OrderDate
) sq
I think! I haven't got access to a SQL box right now to test it on.
Without knowing your structure at all, or your dbms, you would order the results descending by the purchase date/time, and return only the first joined record.
Try TOP 1 With an order by desc on date. Ex:
CREATE TABLE #One
(
id int
)
CREATE TABLE #Many
(
id int,
[date] date,
value int
)
INSERT INTO #One (id)
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3
INSERT INTO #Many (id, [date], value)
SELECT 1, GETDATE(), 1 UNION ALL
SELECT 1, DATEADD(DD, 1 ,GETDATE()), 3 UNION ALL
SELECT 1, DATEADD(DD, -1 ,GETDATE()), 0
SELECT TOP 1 *
FROM #One O
JOIN #Many M ON O.id = M.id
ORDER BY [date] DESC
If you want to select the latest purchase for each person, that would be:
SELECT PE.ID, PE.Name, MAx(PU.pucrhaseDate) FROM Persons AS PE JOIN PURCHASE as PU ON PE.ID = PU.Person_ID
If you want to have all persons also those who have no purchases, you need to use LEFT JOIN.
I think you need one more table called Items for example.
The PERSONS table would uniquely define each person and all their attributes, while the ITEMS table would uniquely define each items and their attributes.
Assume the following:
Persons |Purchases |Items
PerID PerName |PurID PurDt PerID ItemID |ItemID ItemDesc ICost
101 Joe Smith |201 101107 101 301 |301 Laptop 500
|202 101107 101 302 |302 Desktop 699
102 Jane Doe |203 101108 102 303 |303 iPod 199
103 Jason Tut |204 101109 101 304 |304 iPad 499
|205 101109 101 305 |305 Printer 99
One Person Parent may tie to none, one or many Purchase Child.
One Item Parent may tie to none, one or many Purchase Child.
One or more Purchases Children will tie to one Person Parent, and one Item Parent.
select per.PerName as Name
, pur.PurDt as Date
, itm.ItemDesc as Item
, itm.ICost as Cost
from Persons per
, Purchases pur
, Items itm
where pur.PerID = per.PerID -- For that Person
and pur.ItemID = itm.ItemID -- and that Item
and pur.PurDt = -- and the purchase date is
( Select max(lst.PurDt) -- the last date
from Purchases lst -- purchases
where lst.PerID = per.PerID ) -- for that person
This should return:
Name Date Item Cost
Joe Smith 101109 Ipad 499
Joe Smith 101109 Printer 99
Jane Doe 101108 iPod 199