SQL query to get most recent row - sql

I have a Student History table which maintains the enrolled section history for each student. For example, Student X is presently in Section 1 and Student X may have been in other sections in the past (including past enrollment in Section 1).
Each time Student X changes to another section a record is added to the Student History table.
The Student History table has following structure:
Student Id, Date_entered, section_id
I need to write a SQL query to get the records for the following scenario:
Get Student Id of all students CURRENTLY in Sections 1 & 2 (Students most recent date_entered must have been either Sections 1 or 2). The results should not include any students who were in these sections 1 & 2 in the past.
Sample Query:
select student_id from student_Queue_history where section_id in (1, 2)
Can someone help me write query for this one?

You can first select max date for each student and join it back to the student_history table.
with maxdate as (
select student_id, max(date_entered) as mxdate
from student_history
group by student_id)
select s.*
from student_history s
join maxdate m on s.student_id = m.student_id and s.date_entered = m.mxdate
where s.section_id in (1,2)

You have some pretty challenging design flaws with your table but you can leverage ROW_NUMBER for this. This is not the best from a performance perspective but the suboptimal design limits what you can do. Please realize this is still mostly a guess because you haven't provided much in the way of details here.
with CurrentStudents as
(
select *
, ROW_NUMBER() over(partition by student_id order by date_entered desc) as RowNum
from student_Queue_history
)
select *
from CurrentStudents
where section_id in (1, 2)
and RowNum = 1

select a.student_id
from student_Queue_history as a
where a.section_id in (1, 2)
and not exists (select b.student_id from student_Queue_history as b where b.student_id = a.student_id and b.Date_entered > a.Date_entered)

Related

How to create a good request with "max(count(*))"?

I have to say who is the scientist who have been the most in mission. I tried this code but it wasn't successful:
select name
from scientist, mission
where mission.nums = chercheur.nums
having count(*) = (select max(count(numis)) from mission, scientist where
mission.nums = chercheur.nums
group by name)
I have done several modifications for this request but I only obtain errors (ora-0095 and ora-0096 if I remember correctly).
Also, I create my tables with:
CREATE TABLE Scientist
(NUMS NUMBER(8),
NAME VARCHAR2 (15),
CONSTRAINT CP_CHER PRIMARY KEY (NUMS));
CREATE TABLE MISSION
(NUMIS NUMBER(8),
Country VARCHAR2 (15),
NUMS NUMBER(8),
CONSTRAINT CP_MIS PRIMARY KEY (NUMIS),
CONSTRAINT CE_MIS FOREIGN KEY (NUMS) REFERENCES SCIENTIST (NUMC));
You could count the missions each scientist participated in, and wrap that query in a query with a window function that will rank them according to their participation:
SELECT name
FROM (SELECT name, RANK() OVER (PARTITION BY name ORDER BY cnt DESC) AS rk
FROM (SELECT name, COUNT(*) AS cnt
FROM scientist s
JOIN mission m ON s.nums = m.nums
GROUP BY name) t
) q
WHERE rk = 1
Step 0 : Format your code :-) It would make it much easier to visualize
Step 1 : Get the count of Numis by Nums in the Mission table. This will tell you how many missions were done by each Nums
This is done in the cte block cnt_by_nums
Next to get the name of the scientist by joining cnt_by_nums with scientist table.
After that you want to get only those scientists who have the cnt_by_missions as the max available value from cnt_by_num
with cnt_by_nums
as (select Nums,count(Numis) as cnt_missions
from mission
group by Nums
)
select a.Nums,max(b.Name) as name
from cnt_by_nums a
join scientist b
on a.Nums=b.Nums
group by a.Nums
having count(a.cnt_missions)=(select max(a1.cnt_missions) from cnt_by_nums a1)
I'd write a query like this:
SELECT NAME, COUNTER
FROM
(SELECT NAME, COUNT(*) AS COUNTER
FROM SCIENTIST S
LEFT JOIN MISSION M
ON S.NUMS=M.NUMS
GROUP BY NAME) NUM
INNER JOIN
(SELECT MAX(COUNTER) AS MAX_COUNTER FROM
(SELECT NAME, COUNT(*) AS COUNTER
FROM SCIENTIST S
LEFT JOIN MISSION M
ON S.NUMS=M.NUMS
GROUP BY NAME) C) MAX
ON NUM.COUNTER=MAX.MAX_COUNTER;
(it works on MYSQL, I hope it's the same in Oracle)
As you don't select the name of your scientist (only count their missions) you don't need to join those tables within the subquery. Grouping over the foreign key would be sufficient:
select count(numis) from mission group by nums
You column names are a bit weird but that's your choice ;-)
Selecting only the scientist with the most mission references could be achieved in two ways. One way would be your approach where you may get multiple scientists if they have the same max missions.
The first problem you have in your query is that you are checking an aggregation (HAVING COUNT(*) = ) without grouping. You are only grouping your subselect.
Second, you could not aggregate an aggregation (MAX(COUNT)) but you may select only the first row of that subselect ordered by it's size or select the max of it by subquerying the subquery.
Approach with only one line:
select s.name from scientist s, mission m
where m.nums = s.nums
group by name
having count(*) =
(select count(numis) from mission
group by nums
order by 1 desc
fetch first 1 row only)
Approach with double subquery:
select s.name from scientist s, mission m
where m.nums = s.nums
group by name having count(*) =
(select max(numis) from
(select count(numis) numis from mission group by nums)
)
Second approach would be doing the FETCH FIRST on yur final result but this would give you exactly 1 scientist even if there are multiple with the same max missions:
select s.name from scientist s, mission m
where m.nums = s.nums
group by name
order by count(*) desc
fetch first 1 row only
Doing a cartisian product is not state of the art but the optimizer would make it a "good join" with the given reference in the where clause.
Made these with IBM Db2 but should also work on Oracle.
If you want one row, then in Oracle 12+, you can do:
SELECT name, COUNT(*) AS cnt
FROM scientist s JOIN
mission m
ON s.nums = m.nums
GROUP BY name
ORDER BY COUNT(*) DESC
FETCH FIRST 1 ROW ONLY;
In earlier versions, you would generally use a subquery:
SELECT s.*
FROM (SELECT name, COUNT(*) AS cnt
FROM scientist s JOIN
mission m
ON s.nums = m.nums
GROUP BY name
ORDER BY COUNT(*) DESC
) sm
WHERE rownum = 1;
If you want ties, then generally window functions would be a simple solution:
SELECT s.*
FROM (SELECT name, COUNT(*) AS cnt,
RANK() OVER (ORDER BY COUNT(*) DESC) as seqnum
FROM scientist s JOIN
mission m
ON s.nums = m.nums
GROUP BY name
ORDER BY COUNT(*) DESC
) sm
WHERE seqnum = 1;

Counting Attendance Per Student

Software: PowerSchool (Oracle database) Education software
When I run the sql for one student the attendance count in correct. When I add addition students the count becomes the total from all the students, and that total is listed for each student. How do I reset the count after each student or does the subquery go in a different location? Any suggestions appreciated. ~Liz
SELECT s.DCID,
s.lastfirst,
(SELECT COUNT(distinct att.ID)
FROM Students s JOIN Attendance att ON
s.id=att.studentid
JOIN Attendance_code attc ON
attc.id=att.attendance_codeID
WHERE s.id=att.studentid
AND s.id=5538
AND att.Att_Mode_Code='ATT_ModeDaily'
AND att.yearid=27
AND attc.Presence_Status_CD = ('Absent')) AS TAbs
FROM Students s
WHERE s.enroll_tatus=0
AND s.grade_level IN (6, 7, 8)
AND s.id=5538
ORDER BY s.grade_level ASC, s.lastfirst ASC;
Your query should be something like this:
SELECT s.DCID,
s.lastfirst,
(SELECT COUNT(distinct att.ID)
FROM Students s JOIN Attendance att ON
s.id=att.studentid
JOIN Attendance_code attc ON
attc.id=att.attendance_codeID
WHERE s.id=att.studentid
AND s.id=5538
AND att.Att_Mode_Code='ATT_ModeDaily'
AND att.yearid=27
AND attc.Presence_Status_CD = ('Absent')) AS TAbs
FROM Students s
WHERE s.enroll_tatus=0
AND s.grade_level IN (6, 7, 8)
AND s.id=5538
GROUP BY s.DCID, s.lastfirst #You should add this line
ORDER BY s.grade_level ASC, s.lastfirst ASC;
3 things you should change:
Remove both student's IDs filters. You want to see all students, not just the one with ID 5538.
Use different alias for the same table! You are referencing the Students table twice, and both times with the same alias (S).
Link the outmost Student table ID with the subquery's Student table ID, so each COUNT(DISTINCT att.ID) is calculated by student, and not overall.
Here is the edited query:
SELECT
s1.DCID,
s1.lastfirst,
(
SELECT
COUNT(distinct att.ID)
FROM
Students s2 -- Use different alias!
JOIN Attendance att ON s2.id=att.studentid
JOIN Attendance_code attc ON attc.id=att.attendance_codeID
WHERE
att.Att_Mode_Code='ATT_ModeDaily' AND
att.yearid=27 AND
attc.Presence_Status_CD = ('Absent') AND
s1.id = s2.id -- Relate both Student tables
) AS TAbs
FROM
Students s1
WHERE
s1.enroll_tatus=0 AND
s1.grade_level IN (6, 7, 8)
ORDER BY
s1.grade_level ASC,
s1.lastfirst ASC;

Query - limit the number of classes a teacher can teach

I am working on my senior Project. A mini version of an online registration system and website. In the assumptions we want to limit the number of classes a teacher can teach in a semester to 3.
TblEmployee contains: emp_ID (key), can_Teach (first 4 letters of topic).
TblCourse contains: course_ID (Key), course_Type (first 4 letters of topic).
TblClass contains: ClassID (Key), emp_ID (FK), course_ID (FK) semester (period of time)
So far this works fine in selecting the proper teacher that teaches that class:
SELECT *
FROM tblEmployee, tblCourse
WHERE tblEmployee.canTeach = tblCourse.course_Type
AND tblClass.course_ID = ?
However I want to add to it that from tblClass if emp_ID (COUNT) < 3 to include those and exclude those greater than or equal to 3 (i.e. they will not show up in the drop down list.
I tried this but it did not work.
SELECT *
FROM tblEmployee, tblCourse
WHERE tblEmployee.canTeach = tblCourse.course_Type
AND tblClass.course_ID = ?
INNER JOIN (SELECT
tblEmployee.emp_ID, tblClass.emp_ID
FROM
tblEmployee, tblClass
GROUP BY
tblClass.emp_ID
HAVING
count(*) < 3 AND tblClass.emp_ID = tblEmployee.emp_ID)
and semester = ?
Any help would be appreciated.
I would start on the sub query, slim it down, and make your syntax consistent.
SELECT
tblClass.emp_ID
FROM
tblClass
where semester = ?
GROUP BY
tblClass.emp_ID
HAVING count(tblClass.emp_ID) < 3
As I understand it, that is IDs which are in the table less than 3 times (teachers that are teaching less than 3 courses).
For your outer query, you want employees that meets its criteria but are also in the inner set. Again, make your syntax consistent. Then try an IN.
SELECT *
FROM tblEmployee inner join tblCourse
on tblEmployee.canTeach = tblCourse.course_Type
where tblClass.course_ID = ?
and tblEmployee.emp_id
in
{
SELECT
tblClass.emp_ID
FROM
tblClass
where semester = ?
GROUP BY
tblClass.emp_ID
HAVING count(tblClass.emp_ID) < 3
}
See what that does.

cant understand how this view in SQL works

I am having a hard time understand how the create view, TRANSCRIPTVIEW, manages to set the grade of 0 for those who did not take a course. An explanation would help, the solution and question is below. Thanks.
Student(Id,Name)
Transcript(StudId,CourseName,Semester,Grade)
Formulate the following query in SQL:
Create a list of all students (Id, Name) and, for each student, list the average grade for the courses taken in the S2002 semester.
Note that there can be students who did not take any courses in S2002. For these, the average grade should be listed as 0.
Solution:
We first create a view which augments TRANSCRIPT with rows that enroll every student into a NULL course with the grade of 0. Therefore, students who did not take anything in semester ’S2002’ will have the average grade of 0 for that semester.
Below is what confuses me, how does this work and why does it work?
CREATE VIEW TRANSCRIPTVIEW AS (
( SELECT * FROM Transcipt)
UNION
(
SELECT S.Id,NULL,’S2002’,0
FROM Student S)
WHERE S.Id NOT IN (
SELECT T.StudId
FROM Transcript T
WHERE T.Semester = ’S2002’) )
)
Remaining solution:
SELECT S.Id, S.Name, AVG(T.Grade)
FROM Student S, TRANSCRIPTVIEW T
WHERE S.Id = T.StudId AND T.Semester = ’S2002’ GROUP BY S.Id
how the create view, TRANSCRIPTVIEW, manages to set the grade of 0 for those who did not take a course
The set of students who did not take a course in semester S2002 have no record in the transcript table for that semester. Those who did take a course in that semester do have a record in the table for that semester. The query supplies values NULL, 'S2002',0 for students if they are not in the Transcript table for semester S2002:
SELECT S.Id,NULL,’S2002’,0 FROM Student S) -- this parenthesis is wrong
-- this following where conditions looks for students NOT IN the 2002 subset:
WHERE S.Id NOT IN
-- this next part gets a list of studentids for semester 2002
(
SELECT T.StudId FROM Transcript T
WHERE T.Semester = ’S2002’
)
The solution in your qusetion is ridiculous. The better solution is:
SELECT S.Id, S.Name, AVG(case when T.Semester = ’S2002’ then T.Grade end) as AvgS2002Grade
FROM Student S left outer join
TRANSCRIPTVIEW T
on S.Id = T.StudId AND T.Semester = ’S2002’
GROUP BY S.Id
The query in your question is overly complicated. It is using a union (which should really be union all for performance reasons) to be sure all students are included. Gosh, this is what left outer join is for. It is doing filtering in the where clause, when a conditional aggregation is more suitable. It uses archaic join syntax, instead of the ANSI standard.
I hope you are not learning SQL with those shortcomings.

Getting single records back from joined tables that may produce multiple records

I've got a student table and an enrollment table; a student could have multiple enrollment records that can be active or inactive.
I want to get a select that has a single student record and an indicator as to whether that student has active enrollments.
I thought about doing this in an inline UDF that uses the student ID in a join to the enrollment table, but I wonder if there's a better way to do it in a single select statement.
The UDF call might look something like:
Select Student_Name,Student_Email,isEnrolled(Student_ID) from Student
What might the alternative - with one SQL statement - look like?
select Student_Name,
Student_Email,
(select count(*)
from Enrollment e
where e.student_id = s.student_id
) Number_Of_Enrollments
from Student e
will get the number of enrollments, which should help.
Why not join to a secondary select? Unlike other solutions this isn't firing a subquery for every row returned, but gathers the enrollment data for everyone all at once. The syntax may not be quite correct, but you should get the idea.
SELECT
s.student_name,
s.student_email,
IsNull( e.enrollment_count, 0 )
FROM
Students s
LEFT OUTER JOIN (
SELECT
student_id,
count(*) as enrollment_count
FROM
enrollments
WHERE
active = 1
GROUP BY
student_id
) e
ON s.student_id = e.student_id
The select from enrollments could also be redone as a function which returns a table for you to join on.
CREATE FUNCTION getAllEnrollmentsGroupedByStudent()
RETURNS #enrollments TABLE
(
student_id int,
enrollment_count int
) AS BEGIN
INSERT INTO
#enrollments
(
student_id,
enrollment_count
) SELECT
student_id,
count(*) as enrollment_count
FROM
enrollments
WHERE
active = 1
GROUP BY
student_id
RETURN
END
SELECT
s.student_name,
s.student_email,
e.enrollment_count
FROM
Students s
JOIN
dbo.getAllEnrollmentsGroupedByStudent() e
ON s.student_id = e.student_id
Edit:
Renze de Waal corrected my bad SQL!
Try someting like this:
SELECT Student_Name, Student_Email, CAST((SELECT TOP 1 1 FROM Enrollments e WHERE e.student_id=s.student_id) as bit) as enrolled FROM Student s
I think you can also use the exists statement in the select but not positive
try to avoid using udfs or subqueries, they are performance killers. banjolity seems to havea good solution otherwise because it uses a derivd table instead of a UDF or subselect.
select students.name,
decode(count(1), 0, "no enrollments", "has enrollments")
from students, enrollments
where
students.id = enrollments.sutdent_id and
enrollments.is_active = 1 group by students.name
Of course, replace the decode with a function your database uses (or, a case statement).