Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Selecting the minimum of two distinct values

I'm working on a sql selection where I need to get all the smallest records for an ID. My problem is that, while I thought my code was working, it would often return incorrect values for other columns, evidently because of my failure to use having to support my aggregate min function. Here is my code.

SELECT *
FROM example_students
INNER JOIN
(SELECT  id, student, test, subject, MIN(score), semester
FROM example_student_scores
WHERE studentid=94
GROUP BY studentid, test, subject) as scores
ON example_students.id=scores.studentid

This is what I want my code to do.

select from exampletable the minimum score for each distinct test and subject combination where the student has id#94

Here are two (heavily modified) tables for the example (I changed all the column names here and in my code too.

example_students
id    name
----+-----------+
94    Bob
1023  Thomas

.

example_students_scores
id    studentId     test       subject     score       semester
----+-----------+-----------+-----------+-----------+-----------

0    94          quiz        math        46          fall
1    94          quiz        math        71          fall
2    94          quiz        math        63          winter
3    94          midterm     math        94          winter
4    94          midterm     science     72          fall
5    94          quiz        math        50          spring
6    94          final       math        76          spring
7    1023        quiz        math        6           spring
8    1023        quiz        math        52          winter
9    1023        quiz        science     68          fall
..*

and the results should be

results
id    studentId     test       subject     score       semester
----+-----------+-----------+-----------+-----------+-----------
0    94          quiz        math        46          fall
3    94          midterm     math        94          winter
4    94          midterm     science     72          fall
6    94          final       math        76          spring

The problem is, I'll get the wrong values for the semester column (and all the other columns I have in my live work).

Given that this has taken me a long time to get no-where with, here is sql to create the two example databases:

drop table if exists example_students;
drop table if exists example_students_scores;
create table example_students(
    id int(10) primary key,
    name char(25)
);
create table example_students_scores(
    id int(10) not null,
    studentId int(10) not null,
    test char(20),
    subject char(20),
    score int(10) not null default '0',
    semester char(20),
    primary key (id),
    index studentid (studentid)
);
insert into example_students values ('94','Bob');
insert into example_students values ('1023','Thomas');
insert into example_students_scores values ('0'    ,'94'          ,'quiz'        ,'math'        ,'46'          ,'fall');
insert into example_students_scores values ('1'    ,'94'          ,'quiz'        ,'math'        ,'71'          ,'fall');
insert into example_students_scores values ('2'    ,'94'          ,'quiz'        ,'math'        ,'63'          ,'winter');
insert into example_students_scores values ('3'    ,'94'          ,'midterm'     ,'math'        ,'94'          ,'winter');
insert into example_students_scores values ('4'    ,'94'          ,'midterm'     ,'science'     ,'72'          ,'fall');
insert into example_students_scores values ('5'    ,'94'          ,'quiz'        ,'math'        ,'50'          ,'spring');
insert into example_students_scores values ('6'    ,'94'          ,'final'       ,'math'        ,'76'          ,'spring');
insert into example_students_scores values ('7'    ,'1023'        ,'quiz'        ,'math'        ,'6'           ,'spring');

I'd be grateful for any pointers or tips, it's very embarrassing to only figure out that your work is wrong a week after going live!

like image 446
user1772115 Avatar asked Oct 24 '12 19:10

user1772115


2 Answers

In MySQL when you include a field in the select and not in the group by you get an arbitrary value. In this case you can't simply include the semester and test ID in the group by because you won't get the results you want.

In order to accomplish this you must find the min score for each student, test, subject and then join it back on to the orginal table

SELECT * 
FROM   example_students_scores ess 
       INNER JOIN (SELECT studentid, 
                          test, 
                          subject, 
                          Min(score) score 
                   FROM   example_students_scores 
                   WHERE  studentid = 94 
                   GROUP  BY studentid, 
                             test, 
                             subject) scores 
               ON ess.studentid = scores.studentid 
                  AND ess.test = scores.test 
                  AND ess.subject = scores.subject 
                  AND ess.score = scores.score 

SQL Fiddle Demo

Another less conventional method is to self anti join on an inequality.

SELECT 
    s.*
FROM
    example_students_scores s
    LEFT JOIN example_students_scores s2
    ON s.studentID = s2.studentID
      AND s.test = s2.test
      AND s.subject = s2.subject
      AND s.score > s2.score
WHERE 
     s.studentid = 94 
     AND 
     s2.score is null

SQL Fiddle Demo

If you're interested in creating a tie breaker just add an or condition

SELECT 
    s.*
FROM
    example_students_scores s
    LEFT JOIN example_students_scores s2
    ON s.studentID = s2.studentID
      AND s.test = s2.test
      AND s.subject = s2.subject
      AND (s.score > s2.score
           or s.id > s2.id ) -- Added for tie breaker

WHERE 
     s.studentid = 94 
     AND 
     s2.score is null

SQL Fiddle Demo Note: I modified the data to include a tie situation

like image 117
Conrad Frix Avatar answered Sep 20 '22 15:09

Conrad Frix


This should work for you:

select ss2.id score_id,
  ss2.studentid,
  ss1.test,
  ss2.subject,
  ss1.score,
  ss2.semester
from example_students st
left join
(
  select min(score) score, test, subject, studentid
  from example_students_scores
  group by test, studentid, subject
) ss1
  on st.id = ss1.studentid
left join example_students_scores ss2
  on st.id = ss2.studentid
  and ss1.score = ss2.score
  and ss1.test = ss2.test
where st.id = 94
order by ss2.id

See SQL Fiddle with Demo

like image 32
Taryn Avatar answered Sep 21 '22 15:09

Taryn