Subquery to extract values from 4 tables - sql

Some people told me this is not a very difficult one but unfortunately I can't really figure it out. I have the following tables
Person
**ID_Pers** | PersName | City | State | Phone | Email
1950901123893 Michael Houston Texas --- ----
Student
**ID_student** | *ID_Pers* | entry_year | status | exit_year
A123 1950901123893 2014 finished 2017
Subject
**ID_subject** | Subject_name | credit_number | courses | laboratories
Exams
*ID_student* | *ID_subject* | **exam_date** | final_grade
The bold ones are primary keys while the ones that are italicized are foreign keys. I have to extract the students that have passed at least the exams that Michael has passed. I know I have to use subqueries in which I first select the exams that Michael has passed, then select the students that have passed those exams at least. I am having difficulties with this assignment. Can anyone assist me, please?

SELECT * from Exams
INNER JOIN student ON student.ID_student = exams.ID_student
INNER JOIN person ON student.ID_Pers = person.ID_pers
WHERE exams.final_grade >=5 AND person.PersName = "Michael"
I know this is to basically select the exams that Michael has passed. But I need something before it to extract the other people which have passed at least the ones he passed.

You can fetch all the students that have passed the Exams (any exams) and then JOIN the result with the exams fetched in your query. Your final Query should look something like this:
SELECT * from Exams A
INNER JOIN student ON student.ID_student = exams.ID_student
INNER JOIN person ON student.ID_Pers = person.ID_pers
INNER JOIN
(SELECT * from Exams
INNER JOIN student ON student.ID_student = exams.ID_student
INNER JOIN person ON student.ID_Pers = person.ID_pers
WHERE Exams.final_grade >=5 AND person.PersName = "Michael") B ON A.ID_Subject = B.ID_Subject AND A.exam_date = B.exam_date
WHERE A.final_grade >=5;
Hope this helps!

Related

SQL need groups

I want to group unique rows from 1 table that may have many rows in table 2.
If I do make this query:
SELECT c.`lastname`,a.`city`
FROM `customer` c
LEFT JOIN `visit` a ON (c.`id` = a.`id`);
I get results like this:
lastname |city
-------- |----
DOE |Paris
DOE |Miami
Technical|Toronto
Technical|d
M |toronto
mavya |TORONTO
lname |NULL
(Both customer and visit tables also have ids that I'm joining, but not showing here).
How can I get results like below? (Can I get SQL to return an array inside results?)
lastname |city
-------- |----
DOE |Paris,Miami
Technical|Toronto,d
M |toronto
mavya |TORONTO
lname |NULL
Based on your syntax, I am guessing that you are using MySQL (or SQLite).
In either, you can use GROUP_CONCAT():
SELECT c.lastname, GROUP_CONCAT(v.`city`)
FROM customer c LEFT JOIN
visit v
ON c.id = v.id
GROUP BY c.lastname;

Tricky statistics select

Trying to figure out this for a while, but no luck. I have the following tables (MS-SQL 2008):
students
studentID – email – profileID
courses
courseID – name
studentsCourses
studentID – courseID
profiles
profileID – name
profilesMandatoryCourses
profileID - courseID
studentsCoursesLogs
logID - studentID –courseID – accessDate
Each student, when enrolls, is assigned a profile. For each profile there are a number of mandatory course. Those mandatory courses together with any other courses a user takes are saved in the studentsCourses table.
Whenever a student accesses a course the information is logged in the studentsCoursesLogs.
I am trying to figure out all the students that have taken all the mandatory courses based on their profile.
Any pointers appreciated.
I think this should do it (assuming I've understood your data model and requirements correctly - and that in throwing this sample SQL together I've not made any mistakes - I didn't create your data model in a database). I'm pretty sure it should be close enough though.
select studentRecords.StudentId,
sum(case TakenCourseID when null then 0 else 1 end) as CompleteMandatoryCourses,
sum(case TakenCourseID when null then 1 else 0 end) as IncompleteMandatoryCourses
from (
select mandatoryCourses.StudentID, mandatoryCourses.CourseId as MandatoryCourseID, takenCourses.CourseID as TakenCourseID
from ( -- Courses student should have taken - based on their profile
select p.[Profile], pmc.CourseID, s.StudentID
from profiles p
inner join profilesMandatoryCourses pmc on p.ProfileID = pmc.Profile
inner join students s on p.StudentID = s.StudentID
) mandatoryCourses
left join
(
-- Course students have taken
select s.StudentID, s.ProfileID, sc.CourseID
from students s
inner join studentsCourseLogs scl on s.StudentID = scl.StudentID
) takenCourses on mandatoryCourses.ProfileId = takenCourses.ProfileID
and mandatoryCourses.CourseID = takenCourses.CourseID
) studentRecords
group by mandatoryCourse.StudentId
having sum(case TakenCourseID when null then 1 else 0 end) = 0
It will give a recordset like the following....
+----------------+-----------------------------+-------------------------------+
| StudentID | CompleteMandatoryCourses | IncompleteMandatoryCourses |
+----------------+-----------------------------+-------------------------------+
| 1 | 15 | 3 |
| 2 | 8 | 0 |
+----------------+-----------------------------+-------------------------------+
If you just want a list of students who have taken all mandatory courses, you could wrap all of the above as shown here...
select studentID
from ( /* insert very long sql here */ )
where IncompleteMandatoryCourses = 0`

Select Query Joining 3 Tables

I've 3 Tables
Personel : id, name
Department : id, name
Match_Dept_Per : dept_id, pers_id, workInfo
Foreign Keys :
dept_id --> Department.id
pers_id --> Personel.id
Example Data :
Personel :
1, Emir Civas
2, Sercan Tuncay
Department :
1, Sales
2, Planning
Match_Dept_Per :
1,1,Manager
What I'm trying to do is, listing peoples names, their department names and workInfos like:
ID | Pers. Name | Dept Name | Work Info
---------------------------------------
1 | Emir Civas | Sales | Manager
I can do this with a simple select query:
select p.id, p.name, d.name, m.workInfo
from personel p, department d, match_dept_per m
where p.id = m.pers_id and d.id = m.dept_id;
Here is sample fiddle of my schema and this query.
However what I need is to display other persons that their id's are not inserted to match_dept_per table. And Set "Unknown" As the Null Values. Like:
ID | Pers. Name | Dept Name | Work Info
------------------------------------------
1 | Emir Civas | Sales | Manager
2 | Sercan Tuncay | Unknown | Unknown
Since I'm Using Match_Dept_Per Table, If Personel ID isn't Added, I can't do anything.
Any suggestions ?
Use left outer join to include all persons even if they are not associated with the other tables:
select p.id,
p.name,
ifnull(d.name, 'Unknown') DepName,
ifnull(m.workInfo, 'Unknown') workInfo
from personel p
left outer join match_dept_per m
on p.id = m.pers_id
left outer join department d
on d.id = m.dept_id
Here is a demo fiddle.
As you seem to use MS SQL, you might need to use isnull() instead of ifnull(). But I would ommit that anyway because I think it's better to have a NULL in the code where you use the data (Java, C#, whatever). You can control the output there.

Which table exactly is the "left" table and "right" table in a JOIN statement (SQL)?

What makes a given table the left table?
Is it that the table is indicated in the "From" part of the query?
Or, is it the left table because it is on the left hand side of the = operator?
Are the following equivalent
SELECT *
FROM left_table
LEFT JOIN right_table ON left_table.right_id = right_table.id
and
SELECT *
FROM left_table
LEFT JOIN right_table on right_table.left_id = left_table.id
???
Thanks
The Left table is the first table in the select. Yes, your two examples are equivalent.
The right table is always the table that you are joining on. So yes, both of your statements are equivalent.
JOIN [Table] ON ...
[Table] is always the right table.
Roughly "left" is the result of everything that appears first in the whole FROM clause when reading from left to right - including the result of other JOINs, sub-queries, VIEWs and STORED PROCEDURES.
Both SQL statements are equivalent because the = operator at the ON part of the JOIN clause is symmetric (if a = b then b = a) so the result is the same no matter the order.
The regular join shows only the lines where the ON clause of the JOIN is true, while the LEFT JOIN shows also the records from "left" if the condition is false (showing NULL for any column from "right" present in the SELECT).
For example:
-- People: -- Car
id | name owner_id | model
---+------------ ---------+------------
1 | Paul 1 | Ferrari
2 | Nancy 2 | Porsche
3 | Arthur NULL | Lamborghini
4 | Alfred 10 | Maserati
> select people.name, car.model from people join car on car.owner_id=people.id;
name | model
---------+--------------
Paul | Ferrari
Nancy | Porsche
2 record(s) found
> select people.name, car.model from people left join car on
car.owner_id=people.id;
name | model
---------+--------------
Paul | Ferrari
Nancy | Porsche
Arthur | NULL
Alfred | NULL
4 record(s) found
> select people.name, car.model from people left join car on
people.id = car.owner_id;
name | model
---------+--------------
Paul | Ferrari
Nancy | Porsche
Arthur | NULL
Alfred | NULL
4 record(s) found
See this for a pretty good walkthrough on joins: http://en.wikipedia.org/wiki/Join_(SQL)
And yes, both statements are equivalent :-)
Yes, it's determined by the side of the JOIN operator the table appears on. Your two examples are indeed equivalent.
CREATE TABLE ORDERS (
ORDERID INT,
CUSTOMERID INT,
ORDERDATE DATE
);
INSERT INTO ORDERS VALUES (10123,10,DATE '16-08-20');
INSERT INTO ORDERS VALUES (10122,11,DATE '14-09-20');
INSERT INTO ORDERS VALUES (10121,12,DATE '10-10-20');
CREATE TABLE CUSTOMERS (
CUSTOMERID INT,
CUSTOMERNAME VARCHAR(20),
COUNTRY VARCHAR(20)
);
INSERT INTO CUSTOMERS VALUES (11 , 'BUDDHA','INDIA');
INSERT INTO CUSTOMERS VALUES (12 , 'JOHNWIK','UNITED STATES');
INSERT INTO CUSTOMERS VALUES (100, 'SERENA','UNITED KINGDOM');
discussing LEFT JOIN query:
select orders.orderid, customers.customername, orders.orderdate from orders
inner join customers on orders.customerid = customers.customerid;
If you want to know exact left and right tables. From left to right the table attached with from is [left] and table attached with join is [right].
Happy Hacking !!!

find missing values in MySQL

I am trying to write a query to find out what users are not enrolled on some courses.
I have 2 tables; courses, and users. Both contain the fields 'id' and then 'coursename' and username' respectively.
Using these two table the existing system adds users to a third table (called enrolled_users). Users take up one row for each. For example if user id '1' is on 4 courses the enrolled_users looks like this:
-----------------------
| user_id | course_id |
-----------------------
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
-----------------------
As you can see this table data is not built on a relationship, it is generated in PHP using the two tables 'courses' and 'users'.
I am not sure how to write a query on how to find out that courses user '1' is not enrolled in, if there are say 10 courses, but we can see user '1' is only on courses 1-4.
Am I suppose to use something like a LIKE or NOT LIKE?
SELECT u.id, c.id, ec.user_id, ec.course_id
FROM users u, courses c, enrolled_courses ec
WHERE u.id = ec.user_id
AND c.id = ec.course_id
AND u.id = '1'
I have tried using != and <> but this doesn't show what i need; a list of courses the user is not enrolled on.
Sorry if I am being vague, trying to get my head round it!
Using MySQL 5.0, on an existing system I cannot modify, only query from (for the moment).
SELECT
*
FROM
courses c
WHERE
c.id NOT IN (
SELECT
ec.course_id
FROM
enrolled_courses ec
WHERE
ec.user_id = 1 AND ec.course_id IS NOT NULL
)
If you are looking for a specific single user ID, be sure to include that ID condition as part of the WHERE clause, otherwise, leave it blank, and it will give you ALL people who are not registered for ALL possible classes via a Cartesian product (since no direct join between user and courses table) .. this could be VERY large / time consuming if trying to run for everyone and your course table is 100s of courses where someone would only be involved in 2-3-4 courses.
select
u.id,
c.id CourseID
from
users u,
courses c
where
u.id = 1
AND c.id NOT IN
( select ec.Course_ID
from enrolled_courses ec
where ec.user_id = u.id )
Just use a not in and a sub select.
select * from courses c where c.id not in
(select course_id from enrolled_courses where user_id='1')