left Join query in access 2013 - sql

I am new to Microsoft Access. Accept my apology if my question seems trivial.
I am trying to write a query in Access that shows the total number of students enrolled in courses on a monthly basis. I have two tables named course and confirmed_enrollments.
The course table has only one field named course_name whereas the confirmed_enrolments table has three fields; student_code, course_name, and month_of_enrol.
I want to show all course_name (whether students enrolled in it or not) in my query and their total enrollments of the particular month. The query I have written only shows the courses that have enrollment and does not consider the courses which does not have enrollment.
Looking for your help. Here is my SQL code:
SELECT Course.Course_name,
Count(confirmed_enrolments.Student_code) AS CountOfStudent_code,
confirmed_enrolments.Month_of_enrol
FROM Course
LEFT JOIN confirmed_enrolments
ON Course.Course_name = confirmed_enrolments.Course_name
GROUP BY Course.Course_name, confirmed_enrolments.Month_of_enrol
HAVING confirmed_enrolments.Month_of_enrol="December 2016";

I think you intend this:
SELECT c.Course_name, Count(ce.Student_code) AS CountOfStudent_code,
ce.Month_of_enrol
FROM Course as c LEFT JOIN
(SELECT ce.*
FROM confirmed_enrolments as ce
WHERE ce.Month_of_enrol = "December 2016"
) as ce
ON c.Course_name = ce.Course_name
GROUP BY c.Course_name, ce.Month_of_enrol;
The use of the HAVING clause in your query is clever, but you are filtering by the second table -- and the value is NULL rather than the specified month -- when there are no matches.
The issue is that the condition is on the second table. In a normal SQL dialect, you would write:
SELECT c.Course_name, Count(ce.Student_code) AS CountOfStudent_code,
ce.Month_of_enrol
FROM Course as c LEFT JOIN
confirmed_enrolments as ce
ON c.Course_name = ce.Course_name AND
ce.Month_of_enrol = "December 2016"
GROUP BY c.Course_name, ce.Month_of_enrol;
But MS Access doesn't seem to allow the second comparison in a LEFT JOIN.
By the way, another way to write the query uses a correlated subquery:
SELECT c.Course_name,
(SELECDT Count(ce.*)
FROM confirmed_enrolments as ce
WHERE ce.Month_of_enrol = "December 2016"
) AS CountOfStudent_code,
"December 2016" as ce.Month_of_enrol
FROM Course as c ;

Related

Using count aggregate based on the current row on SQL Server

I am using SQL server and trying to get the the course code , title and the number of students who may take that course ( current course) , Knowing that for a student to take the course, he should pay at least half the course price , below are the tables :
I tried the folowing code , and faced the issue of "Column 'courseStudent.paid' is invalid in the HAVING clause because it is not contained in either an aggregate function or the GROUP BY clause.
"
here is the sql code I used :
select courses.code, courses.title , courses.courseId ,
(select count(*) from courseStudent inner join courses on courseStudent.courseId = courses.courseId
group by courseStudent.courseId having courseStudent.paid>courses.price/2)
from courses ;
This should work.
SELECT c.courseId,c.code,c.title, COUNT(1) AS NoOfStudentEnrolled FROM courseStudent cs
JOIN courses c ON cs.courseId = c.courseId
WHERE cs.paid>c.price/2
GROUP BY c.courseId,c.code,c.title

Microsoft SQL Server : max date joining two tables

I am attempting to join two tables while pulling the max date from one.
I have a student table and a communication table. Each student is unique in the student table and has many communication entries.
I would like to create a SQL script that pulls each student's ID, name, latest communication date, and the communication message for that date.
I am able to pull the latest date for each student using max(comm_date) and group by, but things get messy (many duplications) when pulling the corresponding communication message.
Table: Student
studentid, name
Table: Communications
studentid, comm_date, comm_msg
Result:
student.studentid, student.name, communications.comm_date, communications.comm_msg
How can I pull the corresponding communication message given max(comm_date)?
This should get you what you need. I don't know if there's a performance hit doing this via nested subquery, but I like the clean syntax of this:
SELECT
s.studentid,
s.name,
LastCommDate = MAX(c.comm_date),
LastCommMessage = (SELECT comm_msg FROM Communications WHERE studentid = s.studentid AND comm_date = MAX(c.comm_date))
FROM Student AS s
INNER JOIN Communications AS c
ON s.studentid = c.studentid
GROUP BY s.studentid, s.name
This should get what you need...
select
s.studentid,
s.name,
c2.comm_date,
c2.comm_msg
from
Student s
LEFT JOIN
( select
c1.studentid,
max( c1.comm_Date ) as MaxDate
from
Communications c1
group by
c1.studentid ) PreMax
on s.studentid = PreMax.StudentID
LEFT JOIN Communications c2
on PreMax.StudentID = c2.StudentID
AND PreMax.MaxDate = c2.comm_Dat
Additionally, I would suggest adding a column to your student table for most recent communication date (or even an ID if communications has an auto-increment column such as multiple entries on the same day). Then, via an insert trigger to the communications table, you update the student table with that newest date (or communication ID). Then you never need to keep requerying the MAX() and re-joining multiple times as this one does.

SQL count after joining two tables

I am newbie to SQL, I would like to come up with a count, assume this example with 2 tables:
School(schoolID, name,....)
Student(StudentID, SchoolID, ...)
I tried:
SELECT COUNT(studentID)
FROM School s, Student t
WHERE s.schooldID = t.schoolID
How can I get a count of all students across all schools?
Since you have the school ID in the student table, it doesn't appear to me that you need to join to school at all. Just select a count from the student table and group by schoolID:
SELECT schoolID, COUNT(*) AS numStudents
FROM student
GROUP BY schoolID;
The only reason you'd need to join to School is if you want other information, such as school name and so on. If you just want the school id and number of students, the above will work.
To complete that last thought, possibly irrelevant to your question. If you did want school name, you just do an inner join and put school.name in your select statement, along with the count from the student table and group by school ID still:
SELECT s.name, st.COUNT(*) AS numStudents
FROM student st
JOIN school s ON s.id = st.schoolID
GROUP BY s.id;
If you want to get the count per school, you need a group by. Also, usually we prefer ANSI style joins, since in fact all database systems support them nowadays and they easier to read and maintain:
select count(t.studentID)
from Student t
join School s /* added join for your convenience, not necessary here */
on s.schooldID = t.schooldID
group
by t.schoolID

ORACLE AVG function query

I have four tables with the following construct:-
I am trying to construct a query which will output offerings which have an attendance below the average attendance for offerings of the course to which they belong. I have constructed two queries so far
This outputs the total number of attendees for each course
This outputs the total number of offerings for each course.
I think what i need to do is divide the results of the first query, by the results of the second query (which will give me the average attendance for each offering of each course) and then output only the offerings with attendance below that result. I really am struggling to build this query so I am basically looking for some help
Any help is much appreciated as always
One way to do it is to first find the number of attendees for each offering, then from this result find the average attendance for each course, join the average attendances to each related offering, and then select the ones where the actual attendance is lover than the average.
This can be done using a CTE:
WITH attendee_counts AS
(SELECT c.course_id, o.offering_id,
COUNT (Student_id) AS attendees -- find attendance
FROM course c
INNER JOIN offering o
ON o.course_id = c.course_id
LEFT JOIN attendance a
ON a.offering_id = o.offering_id
GROUP BY c.course_id, o.offering_id) -- for each offering
SELECT ac.course_id, ac.offering_id,
ac.attendees, avgs.avg_attendees
FROM attendee_counts AS ac
INNER JOIN
(SELECT course_id, AVG(attendees) AS avg_attendees -- then average
FROM attendee_counts
GROUP BY course_id) AS avgs -- by course
ON avgs.course_id = ac.course_id
WHERE ac.attendees < avgs.avg_attendees;
The query (that works in PostgreSQL) can be tested here: http://www.sqlfiddle.com/#!1/f5b60/20/0
Edit:
Oracle seems to require a slightly different solution:
WITH attendee_counts AS
(SELECT c.course_id, o.offering_id,
COUNT (Student_id) AS attendees
FROM course c
INNER JOIN offering o ON o.course_id = c.course_id
LEFT JOIN attendance a ON a.offering_id = o.offering_id
GROUP BY c.course_id, o.offering_id)
SELECT o.course_id, o.offering_id, o.attendees,
avg(c.attendees) AS avg_attendees
FROM attendee_counts o -- connect attendance by offering
LEFT JOIN attendee_counts c
ON c.course_id = o.course_id -- to each offering of the same course
GROUP BY o.course_id, o.offering_id, o.attendees
HAVING o.attendees < avg(c.attendees);
This can be tested here http://www.sqlfiddle.com/#!4/e50e4/4/0 (for Oracle 11g R2)

SQL Query for finding values that do not exist in one table, with WHERE clause

I'm struggling to compile a query for the following and wonder if anyone can please help (I'm a SQL newbie).
I have two tables:
(1) student_details, which contains the columns: student_id (PK), firstname, surname (and others, but not relevant to this query)
(2) membership_fee_payments, which contains details of monthly membership payments for each student and contains the columns: membership_fee_payments_id (PK), student_id (FK), payment_month, payment_year, amount_paid
I need to create the following query:
which students have not paid fees for March 2012?
The query could be for any month/year, March is just an example. I want to return in the query firstname, surname from student_details.
I can query successfully who has paid for a certain month and year, but I can't work out how to query who has not paid!
Here is my query for finding out who has paid:
SELECT student_details.firstname, student_details.surname,
FROM student_details
INNER JOIN membership_fee_payments
ON student_details.student_id = membership_fee_payments.student_id
WHERE membership_fee_payments.payment_month = "March"
AND membership_fee_payments.payment_year = "2012"
ORDER BY student_details.firstname
I have tried a left join and left outer join but get the same result. I think perhaps I need to use NOT EXISTS or IS NULL but I haven't had much luck writing the right query yet.
Any help much appreciated.
I'm partial to using WHERE NOT EXISTS Typically that would look something like this
SELECT D.firstname, D.surname
FROM student_details D
WHERE NOT EXISTS (SELECT * FROM membership_fee_payments P
WHERE P.student_id = D.student_id
AND P.payment_year = '2012'
AND P.payment_month = 'March'
)
This is know an a correlated subquery as it contains references to the outer query. This allows you to include your join criteria in the subquery without necessarily writing a JOIN. Also, most RDBMS query optimizers will implement this as a SEMI JOIN which does not typically do as much 'work' as a complete join.
You could use a left join. When the payment is missing, all the columns in the left join table will be null:
SELECT student_details.firstname, student_details.surname,
FROM student_details
LEFT JOIN membership_fee_payments
ON student_details.student_id = membership_fee_payments.student_id
AND membership_fee_payments.payment_month = "March"
AND membership_fee_payments.payment_year = "2012"
WHERE membership_fee_payments.student_id is null
ORDER BY student_details.firstname
You can also write following query. This will gives your expected output.
SELECT student_details.firstname,
student_details.surname,
FROM student_details
Where
student_details.student_id Not in
(SELECT membership_fee_payments.student_id
from membership_fee_payments
WHERE
membership_fee_payments.payment_year = '2012'
AND membership_fee_payments.payment_month = 'March'
)