Mins of Max SQL - mysql

Semesters Table
+----+------+
| ID | Name |
+----+------+
| 1 | 1st |
| 2 | 2nd |
+----+------+
Subjects Table
+----+-------------+-------------+
| ID | Semester Id | Name |
+----+-------------+-------------+
| 1 | 1 | Mathematics |
| 2 | 1 | English |
| 3 | 2 | Mathematics |
| 4 | 2 | English |
+----+-------------+-------------+
Tests Table
+----+------------+-------+
| ID | Subject ID | Score |
+----+------------+-------+
| 1 | 1 | 70 |
| 2 | 1 | 75 |
| 3 | 2 | 75 |
| 4 | 2 | 70 |
| 5 | 3 | 75 |
| 6 | 3 | 70 |
| 7 | 4 | 70 |
| 8 | 4 | 75 |
+----+------------+-------+
I can get the scores of the 2nd test by using MAX on the ID of the tests, and then grouping them by the subject id. However, then I have to get the minimums of the scores grouped by the semester.
Is it possible to get the lowest scoring 2ND test of each semester in a single SQL statement?
The result set would look like this.
+----------+-------------+-------+
| Semester | Subject | Score |
+----------+-------------+-------+
| 1st | English | 70 |
| 2nd | Mathematics | 70 |
+----------+-------------+-------+
This is in MySQL.

You are looking for the second lowest tests per semester.
Build row numbers for the ordered tests per semester and stay with those numbered #2. One way to do this is a correlated subquery. Another would be variables.
select
sem.name as semester,
sub.name as subject,
tst.score
from semesters sem
join subjects sub on sub.semester_id = sem.id
join tests tst on tst.subject_id = sub.id
where
(
select count(*)
from subjects sub2
join tests tst2 on tst2.subject_id = sub2.id
where sub2.semester_id = sub.semester_id
and sub2.id <= sub.id
and tst2.score <= tst.score
) = 2
order by sub.semester_id;
In case of ties one of the rows is picked, just as shown in your example.
Working with variables is probably faster than above query. You will easily find the method by looking for how to emulate ROW_NUMBER in MySQL. (Other DBMS use ROW_NUMBER which is much simpler, but MySQL doesn't feature this function.)

select a.name as semester
, b.name as subject
, min(c.score) as lowestscore
from subjects as b
join semesters as a on a.id = b.semester_id
join tests as c on c.subject_id = b.id
group by a.name, b.name
You will probably want to order by or something but this should give you what you are looking for. You could add more discriminators for range of semesters or subjects, but this will yield the name of the semester, the name of the subject and the minimum score for that semester/subject.

Related

MySQL Selecting Rows and Grouping

I have a ( Joomla) database table called field_values, the contents are below;
+----+----------+---------+---------+
| id | field_id | item_id | value |
+----+----------+---------+---------+
| 1 | 2 | 446 | Jones |
| 2 | 2 | 447 | Smith |
| 3 | 2 | 448 | Jenkins |
| 4 | 3 | 446 | Paul |
| 5 | 3 | 447 | Peter |
| 6 | 3 | 448 | Sally |
| 7 | 4 | 446 | London |
| 8 | 4 | 447 | Dublin |
| 9 | 4 | 448 | Paris |
+----+----------+---------+---------+
I'm only displaying 9 rows from the table, but I actually have thousands, so the successful query would need to take this into account.
Columns explained;
id (primary / auto-increment)
field_id (FK to another fields table, 2 = surname, 3 = first name, 4 = location)
item_id (FK to another users table)
value (contents of field)
How can I select all the values from the above table but display them as follows;
+------------+-----------+----------+
| first_name | last_name | location |
+------------+-----------+----------+
| Paul | Jones | London |
| Peter | Smith | Dublin |
| Sally | Jenkins | Paris |
+------------+-----------+----------+
The id field isn't really necessary in the desired results above, I just added it to emphasise that each row is unique.
I'm not sure if I need to use a subquery or group by, maybe neither?
Thanks in advance.
A pivot query should work here:
SELECT
MAX(CASE WHEN field_id = 3 THEN value END) AS first_name,
MAX(CASE WHEN field_id = 2 THEN value END) AS last_name,
MAX(CASE WHEN field_id = 4 THEN value END) AS location
FROM yourTable
GROUP BY
item_id
ORDER BY
item_id;
Your current table structure is a denormalized key value store, a style which WordPress uses in some of its tables.
you could avoid subquery and grou by.
You could use the same table 3 times
select a.id, b.value firts_name, a.value last_name , c.value location
from field_values a
inner join field_values b on a.item_id = b.item_id and b.field_id = 3
inner join field_values bc on a.item_id = c.item_id and b.field_id = 4
where a.item_id = 2

How to get all rows of one table after joining?

I have a database with 3 tables: students, courses and mistakes. I have one joining table (csm) where I connect the 3 tables. I am supposing mistakes are the same for each course.
Table Courses
+----------+---------------+
| crs_id | crs_name |
+----------+---------------+
| 1 | HTML |
| 2 | PHP |
| 3 | Python |
+----------+---------------+
Table Students
+----------+---------------+---------------+
| stu_id | stu_firstname | stu_lastname |
+----------+---------------+---------------+
| 1 | Tina | Turner |
| 2 | Lisa | Laroi |
| 3 | Dina | Donna |
| 3 | Jim | Leduc |
+----------+---------------+---------------+
Table Mistakes
+----------+---------------+------------+
| mis_id | mis_name | mis_weight |
+----------+---------------+------------+
| 1 | No camelCase | 7 |
| 2 | No brackets | 10 |
| 3 | Operator mist.| 12 |
+----------+---------------+------------+
Joining table CSM
+----------+------------+------------+------------+
| csm_id | fk_crs_id | fk_stu_id | fk_mis_id |
+----------+------------+------------+------------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 1 | 3 |
| 3 | 2 | 3 | 1 |
| 4 | 3 | 2 | 2 |
| 5 | 3 | 2 | 1 |
| 6 | 3 | 3 | 1 |
+----------+------------+------------+------------+
If I select a specific course, I want to get a list of ALL students with the minus points for this course. So I also want to get the students with no result in the joining table csm.
The closest result I got is with the following sql statement:
select stu_firsname, stu_lastname, csm.*, sum(mis_weight)
from students s
left join crs_stu_mis csm on s.stu_id = csm.fk_stu_id
left join mistakes m on csm.fk_mis_id = m.mis_id
where fk_crs_id = 4 or fk_crs_id is null
group by stu_firstname;
With this I get the sum of the mistakes for a certain course and also the students who don't have any records in CSM table, but some results are missing. For example, this doesn't show the students who have records in the CSM table, but not for the requested course.
How do I get these students in my result table?
In your query :
left join crs_stu_mis csm on s.stu_id = csm.fk_stu_id`
...
where fk_crs_id = 4 or fk_crs_id is null
This is not exactly what you want, since this condition will filter out students that have records in the csm table for only courses other than 4. You want to move that condition to the corresponding LEFT JOIN:
left join crs_stu_mis csm on s.stu_id = csm.fk_stu_id AND csm.fk_crs_id = 4
Another potential source of problems is the way the query handles aggregation. There are non-aggregated columns in the SELECT clause that do not appear in the GROUP BY clause. This syntax is not good SQL coding practice, and is not supported anymore since version 5.7 of MySQL. I assumed that you want to one record in the result for each student.
Query:
select
s.stu_firstname,
s.stu_lastname,
sum(m.mis_weight) total_misses_weight
from
students s
left join crs_stu_mis csm on s.stu_id = csm.fk_stu_id AND csm.fk_crs_id = 3
left join mistakes m on csm.fk_mis_id = m.mis_id
group by
s.stu_id,
s.stu_firstname,
s.stu_lastname
Demo on DB Fiddle for course id 3:
| stu_firstname | stu_lastname | total_misses_weight |
| ------------- | ------------ | ------------------- |
| Tina | Turner | |
| Lisa | Laroi | 17 |
| Dina | Donna | 7 |
| Jim | Leduc | |
if your primary data's is in CSM table, try this:
select s.stu_firsname, s.stu_lastname, csm.*, sum(m.mis_weight) from crs_stu_mis csm
join students s on s.stu_id = csm.fk_stu_id
join mistakes m on csm.fk_mis_id = m.fou_id
csm.fk_crs_id = 4
group by s.stu_naam;
Second case:
Your data's can affected by group by attribute, try attribute that it is not NULL , ex:
select s.stu_firsname, s.stu_lastname, csm.*, sum(m.mis_weight) from crs_stu_mis csm
join students s on s.stu_id = csm.fk_stu_id
join mistakes m on csm.fk_mis_id = m.fou_id
csm.fk_crs_id = 4
group by csm.csm_id;

MySQL How do I get the sum of multiple rows from multiple tables and then order the results by total descending?

I am trying to build a leaderboard based on the points (money) a user has. The points are stored in multiple tables and some users may not have points in a given table.
Table: account
--------------------------
| uid | name | locker |
|-----|---------|--------|
| 1 | Bob | 15 |
| 2 | Dave | 2 |
| 3 | Jim | 5 |
--------------------------
Table: container
------------------------
| account_uid | money |
|-------------|--------|
| 1 | 4 |
| 3 | 1 |
| 3 | 2 |
| 3 | 4 |
------------------------
Table: vehicle
------------------------
| account_uid | money |
|-------------|--------|
| 2 | 2 |
| 2 | 1 |
| 3 | 2 |
------------------------
I would like to see the results ouput as -
Bob 19
Jim 14
Dave 5
Note that some tables do not have points for some people.
This code did not work for me. It seems to have duplicated the points somehow.
SELECT
act.name,
act.uid,
SUM(COALESCE(act.locker,0) + COALESCE(con.money,0) + COALESCE(veh.money,0)) AS total
FROM account as act
LEFT JOIN container as con
ON act.uid = con.account_uid
LEFT JOIN vehicle as veh
ON act.uid = veh.account_uid
Group By act.name
ORDER BY total DESC
How about calculating the totals one at a time and then combining them into one?
select account.uid, account.name, sum(tot.Money) as TotalMoney from
(
(select uid, sum(Money) as Money from container
group by uid)
union all
(select uid, sum(Money) as Money from vehicle
group by uid)
) tot
inner join account on
tot.uid = account.uid
group by account.uid

MySQL Selecting all records from one table and their matches/NULL from another table

I have three tables. Two of them are separate irrelevant tables (students and subjects), the third (entries) is one which links them both with foreign keys (student_id and subject_id).
Here are all the tables with the records:
students:
+------------+------------+-----------+---------------------+---------------------+
| student_id | first_name | surname | email | reg_date |
+------------+------------+-----------+---------------------+---------------------+
| 1 | Emily | Jackson | emilym#gmail.com | 2012-10-14 11:14:13 |
| 2 | Daniel | ALexander | daniela#hotmail.com | 2014-08-19 08:08:23 |
| 3 | Sarah | Bell | sbell#gmail.com | 1998-07-04 13:16:32 |
| 4 | Alex | Harte | AHarte#hotmail.com | 1982-06-14 00:00:00 |
+------------+------------+-----------+---------------------+---------------------+
subjects:
+------------+--------------+------------+----------------+
| subject_id | subject_name | exam_board | level_of_entry |
+------------+--------------+------------+----------------+
| 1 | Art | CCEA | AS |
| 2 | Biology | CCEA | A |
| 3 | Computing | OCR | GCSE |
| 4 | French | CCEA | GCSE |
| 5 | Maths | OCR | AS |
| 6 | Chemistry | CCEA | GCSE |
| 7 | Physics | OCR | AS |
| 8 | RS | CCEA | GCSE |
+------------+--------------+------------+----------------+
entries:
+----------+---------------+---------------+------------+
| entry_id | student_id_fk | subject_id_fk | entry_date |
+----------+---------------+---------------+------------+
| 1 | 1 | 1 | 2012-10-15 |
| 2 | 1 | 4 | 2011-09-21 |
| 3 | 1 | 3 | 2015-08-10 |
| 4 | 2 | 6 | 1992-07-13 |
| 5 | 3 | 7 | 2013-02-12 |
| 6 | 3 | 8 | 2016-01-14 |
+----------+---------------+---------------+------------+
How would I go about selecting the names of all the students (students.first_name), and the name (subjects.subject_name) of whichever subject entries they have? What I mean is that all student names are returned, and all the subject names for their entries listed beside them, with NULL for those which don't have any entries. I also want it grouped by the first_name alphabetically.
I want the output to be something like:
first_name subject_name
--------------------------
Alex NULL
Daniel Chemistry
Emily French
Emily Computing
Sarah Physics
Sarah RS
though I'm not sure if that's entirely correct.
My guess is it's like this:
SELECT students.first_name, subjects.subject_name
FROM students
JOIN entries ON entries.student_id_fk = students.student_id
JOIN subjects ON entries.subject_id_fk = subjects.subject_id
GROUP BY students.first_name;
Really the only thing I'm not sure about is which join to use and where to put it, or if the tables need to be listed in a different order. I'm thinking it should be a left join but I'm not sure.
Help is much appreciated!
You should use left join since you want to return also row for student that has no subject assigned. Plus you want to sort the result, not group. So in the end should be like this:
SELECT students.first_name, subjects.subject_name
FROM students
LEFT JOIN entries ON entries.student_id_fk = students.student_id
LEFT JOIN subjects ON entries.subject_id_fk = subjects.subject_id
ORDER BY students.first_name;
I think this could be the query but use order by and not group by (group by is for aggregate function like count() or max() )
SELECT students.first_name, subjects.subject_name
FROM entries
LEFT JOIN students ON entries.student_id_fk = students.student_id
LEFT JOIN subjects ON entries.subject_id_fk = subjects.subject_id
ORDER BY students.first_name;
You likely want a LEFT OUTER JOIN:
select stu.first_name, sub.subject_name
from students stu
join entries e
on stu.student_id = e.student_id_fk
left outer join subjects sub
on sub.subject_id = e.subject_id_fk
order by stu.first_name;
I'm away from my PC with any SQL installed, so this is untested, but should give you what you want.
Modified my answer to replace the group by with an order by as pointed out by the others. That is the proper way to do it, unless the group is there as part of a larger query that you may have trimmed out.
Left join students table on the remaining two tables sequentially is the right choice to grab all the students first name.
To order the names order by should be used.
I have create a sql fiddle for better visualization of how to get the desired output.
Here is the link: http://sqlfiddle.com/#!9/a1c850/7
The query is
select students.student_name, subjects.subject_name
from
students
left join entries on entries.student_id=students.student_id
left join subjects on entries.subject_id=subjects.subject_id
order by students.student_name;

Sum up values in SQL once all values are available

I have events flowing into a MySQL database and I need to group and sum the events to transactions and store away into another table. The data looks like:
+----+---------+------+-------+
| id | transid | code | value |
+----+---------+------+-------+
| 1 | 1 | b | 12 |
| 2 | 1 | i | 23 |
| 3 | 2 | b | 34 |
| 4 | 1 | e | 45 |
| 5 | 3 | b | 56 |
| 6 | 2 | i | 67 |
| 7 | 2 | e | 78 |
| 8 | 3 | i | 89 |
| 9 | 3 | i | 90 |
+----+---------+------+-------+
The events arrive in batches and I would like to create the transaction by summing up the values for each transid, like:
select transid, sum(value) from eventtable group by transid;
but only after all the events for that transid have arrived. That is determined by the event with the code e (b for the beginning, e for the end and i for varying amount of intermediates). Being a novice in SQL, how could I implement the requirement for the existance of the end code before the summing?
Perhaps with having:
select transid, sum(value)
from eventtable
group by transid
having max(case code when 'e' then 1 end)=1;
select transid, sum(value) from eventtable
group by transid
HAVING COUNT(*) = 3
you should count the records in the group. So when there is (b)egin, (i)?? don't know what it is and (e)nd this group is not filtered out.