MySQL - Student grade list, replace small grade by bonus grade - mysql

SELECT TestID, Grade FROM tests_points;
Returns:
+--------+-------+
| TestID | Grade |
+--------+-------+
| 10 | 125 |
| 11 | 110 |
| 12 | 100 |
| 13 | 75 |
| 14 | 50 |
| 15 | 65 |
| 16 | 70 |
| 17 | 100 |
| 18 | 100 |
+--------+-------+
But, tests ID 17 and 18 are "bonus tests", so I need replace the two lower grades by these two ones, and return the SUM of all grades.
So, how I can "replace"the two lower grades (From TestID 14 and 15) by testID 17 and 18 grades.
The "correct grade list" would be:
+--------+-------+
| TestID | Grade |
+--------+-------+
| 10 | 125 |
| 11 | 110 |
| 12 | 100 |
| 13 | 75 |
| 14 | 100|
| 15 | 100|
| 16 | 70 |
+--------+-------+
In the end I just need the SUM of all grades, fixing the lower grades.
SELECT SUM(Grade) FROM tests_points;
How can I do that?

This is what you need!
select testID, GREATEST(
IF(
grade = (select min(grade) from test_points),(select grade from test_points where testID = 17),
grade)
,
IF(grade = (select min(grade) from test_points WHERE grade > ( SELECT min(grade) FROM test_points))
,(select grade from test_points where testID = 18),
grade)) as Score
from test_points
where testID not in(17, 18)
Demo on SQLfiddle.com

the following query will get probably get what you want..
SELECT ttal-garba FROM
(SELECT sum(c.grade) as garba FROM (SELECT * from tests_points where testid < 17 order by grade asc limit 2) as c) as a,
(SELECT sum(grade) as ttal FROM tests_points) as b
(I know.. hard coding everything is bad ..)

If your just trying to add up all the values except the bottom two you can do something lik,e this.
First add a new field lets say test_type:
UPDATE test_points
SET test_type = 'test';
Now we can just select using group by the sum. For example
SELECT test_type, (SUM(Grade) OVER (ORDER BY Grade DESC))
FROM test_Points
LIMIT 7;
This will return the sum of all values, but it will show 7 columns. If you want only the total, Throw a group by into the mix, and it will narrow it down to one field, or just select the 7th row number.

Related

Show first and last mysql record in group with other attributes

I would like to ask for help to display some information regarding queries made in a database. I have a table called membervalues. In this table, I have 4 fields => (id, memberid, submit_datetime, value). I want to show maximum and minimum value of each member based on record id. For example, I have following
id | memberid | submit_datetime | value
------------------------------------------------
1 | 1 | 2018-01-18 09:44:00 | 86
2 | 2 | 2018-01-18 10:32:00 | 95
3 | 3 | 2018-01-18 11:19:00 | 74
4 | 1 | 2018-01-18 17:57:00 | 84
5 | 3 | 2018-01-18 18:31:00 | 96
7 | 1 | 2018-01-19 06:31:00 | 86
8 | 2 | 2018-01-19 07:31:00 | 94
9 | 1 | 2018-01-19 08:31:00 | 87
What one query will return the following result in php loop?
memberid | min_record_value | max_record_value
------------------------------------------------
1 | 86 | 87
2 | 95 | 94
3 | 74 | 96
I can get the min and max id but through min(id) and max(id) with GROUP BY memberid, but I am not able to figure how to get record value with it as I required above. While searching I came across mysql - join first and last records by group type? and tried to opt for similar approach with
select x2.memberid, x2.value as min_record_value, y2.value as max_record_value
from (select *
from (select memberid, value
from membervalues
order by 1, 2) x1
group by 1) x2
join (select *
from (select memberid, value
from membervalues
order by 1, 2 desc) y1
group by 1) y2 on y2.memberid = x2.memberid
but it returns only first rows value for both as below.
memberid | min_record_value | max_record_value
------------------------------------------------
1 | 86 | 86
2 | 95 | 95
3 | 74 | 74
Any help will be greatly appreciated. Thanks
You can use the row_number() window function to assign a number, starting from 1, to each record for each memberid depending on the position of the record when ordered by submit_datetime and id (to resolve possible ties). Once in ascending order for the minimum and once in descending order for the maximum. Then you can join the derived table on common memeberid and that number and filter for that number being 1.
SELECT xmi.memberid,
xmi.value min_record_value,
xma.value max_record_value
FROM (SELECT v1.memberid,
v1.value,
row_number() OVER (PARTITION BY v1.memberid
ORDER BY v1.submit_datetime ASC,
v1.id ASC) rn
FROM membervalues v1) xmi
INNER JOIN (SELECT v2.memberid,
v2.value,
row_number() OVER (PARTITION BY v2.memberid
ORDER BY v2.submit_datetime DESC,
v2.id DESC) rn
FROM membervalues v2) xma
ON xma.memberid = xmi.memberid
AND xma.rn = xmi.rn
WHERE xma.rn = 1
AND xmi.rn = 1;

Multi-event tournament standings (with arbitrary number of entries)

Suppose you have a a multi-event competition where competitors can attempt any event an arbitrary number of times. (weird, I know.)
How do pull out a desired player's best time for each event,
and assign it a placing? (1st 2nd 3rd...)
Data example: Desired output:
Name | Event | Score Name | Event | Score | Rank
-------------------- ----------------------------
Bob 1 50 Given input: "Bob"
Bob 1 100 Bob 1 100 1
Bob 2 75 Bob 2 75 3
Bob 3 80 Bob 3 80 2
Bob 3 65
Given input: "Jill"
Jill 2 75 Jill 2 90 1
Jill 2 90 Jill 3 60 3
Jill 3 60
Given input: "Chris"
Chris 1 70 Chris 1 70 2
Chris 2 50 Chris 2 85 2
Chris 2 85 Chris 3 100 1
Chris 3 100
This is a build up of my previous question:
Multi-event tournament standings
I feel understand that problem much better (Thanks!), but I cannot bridge the gap to this version of the problem.
I have SQL 5.x so I cant use stuff like Rank(). This will also be crunching many thousands of scores.
Desired output can be acheaved with this query:
select
IF(event is NULL, CONCAT('Given input: "', name,'"'), name) as name,
IF(event is NULL, '', event) as event,
IF(event is NULL, '', max(score)) as score,
IF(event is NULL, '', (
select count(s2.name) + 1
from (
select name, max(score) as score
from scores es
where es.event = s.event
group by es.name
order by score desc
) s2
where s2.score > max(s.score)
)) as `rank`
from scores s
group by name, event with rollup
having name is not NULL
order by name, event;
And output (if run query in mysql cli):
+----------------------+-------+-------+------+
| name | event | score | rank |
+----------------------+-------+-------+------+
| Given input: "Bob" | | | |
| Bob | 1 | 100 | 1 |
| Bob | 2 | 75 | 3 |
| Bob | 3 | 80 | 2 |
| Given input: "Chris" | | | |
| Chris | 1 | 70 | 2 |
| Chris | 2 | 85 | 2 |
| Chris | 3 | 100 | 1 |
| Given input: "Jill" | | | |
| Jill | 2 | 90 | 1 |
| Jill | 3 | 60 | 3 |
+----------------------+-------+-------+------+
11 rows in set, 3 warnings (0.00 sec)
Should work on any Mysql 5.
You can get the highest score per event by an aggregation by event taking the max(). To simulate a dense_rank() you can use a subquery counting the scores higher than or equal to the current score per event.
For a particular contestant (here Bob) that makes:
SELECT d1.name,
d1.event,
max(d1.score) score,
(SELECT count(*)
FROM (SELECT d2.event,
max(d2.score) score
FROM data d2
GROUP BY d2.event,
d2.name) x1
WHERE x1.score >= max(d1.score)
AND x1.event = d1.event) rank
FROM data d1
WHERE d1.name = 'Bob'
GROUP BY d1.event
ORDER BY d1.event;
And for all of them at once:
SELECT d1.name,
d1.event,
max(d1.score) score,
(SELECT count(*)
FROM (SELECT d2.event,
max(d2.score) score
FROM data d2
GROUP BY d2.event,
d2.name) x1
WHERE x1.score >= max(d1.score)
AND x1.event = d1.event) rank
FROM data d1
GROUP BY d1.name,
d1.event
ORDER BY d1.name,
d1.event;
db<>fiddle
E.g.:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,name VARCHAR(12) NOT NULL
,event INT NOT NULL
,score INT NOT NULL
);
INSERT INTO my_table (name,event,score) VALUES
('Bob' ,1, 50),
('Bob' ,1,100),
('Bob' ,2, 75),
('Bob' ,3, 80),
('Bob' ,3, 65),
('Jill' ,2, 75),
('Jill' ,2, 90),
('Jill' ,3, 60),
('Chris',1, 70),
('Chris',2, 50),
('Chris',2, 85),
('Chris',3,100);
SELECT a.*
, FIND_IN_SET(a.score,b.scores) my_rank
FROM my_table a -- it's possible that this really needs to be a repeat of the subquery below, so
-- ( SELECT m.* FROM my_table m JOIN (SELECT name,event,MAX(score) score FROM my_table
-- GROUP BY name, event) n ON n.name = m.name AND n.event = m.event AND n.score = m.score) AS a
JOIN
(
SELECT x.event
, GROUP_CONCAT(DISTINCT x.score ORDER BY x.score DESC) scores
FROM my_table x
JOIN
( SELECT name
, event
, MAX(score) score
FROM my_table
GROUP
BY name
, event
) y
ON y.name = x.name
AND y.event = x.event
AND y.score = x.score
GROUP
BY x.event
) b
ON b.event = a.event
WHERE FIND_IN_SET(a.score,b.scores) >0;
+----+-------+-------+-------+------+
| id | name | event | score | rank |
+----+-------+-------+-------+------+
| 2 | Bob | 1 | 100 | 1 |
| 3 | Bob | 2 | 75 | 3 |
| 4 | Bob | 3 | 80 | 2 |
| 6 | Jill | 2 | 75 | 3 |
| 7 | Jill | 2 | 90 | 1 |
| 8 | Jill | 3 | 60 | 3 |
| 9 | Chris | 1 | 70 | 2 |
| 11 | Chris | 2 | 85 | 2 |
| 12 | Chris | 3 | 100 | 1 |
+----+-------+-------+-------+------+

How to loop table rows and get header - value pair on MySQL

For example I got table like this:
ID | GroupID | Fee | Discount
-----------------------------
1 | 20 | 60 | 15
2 | 21 | 55 | 42
I want to loop each row and get header - value pair like this:
From row 1: GroupID - 20, Fee - 60, Discount - 15
From row 2: GroupID - 21, Fee - 55, Discount - 42
So basically, this is my old table and I want to convert it to the new one. The new one will look like this:
ID | GroupID | Type | Value
-------------------------------
1 | 20 | Fee | 60
2 | 20 | Discount | 15
3 | 21 | Fee | 55
4 | 21 | Discount | 42
SELECT
t.ID,
t.GroupID,
a.Type,
IF(a.Type = 'Fee', Fee, Discount) AS `Value`
FROM t
CROSS JOIN (SELECT 'Fee' AS Type UNION SELECT 'Discount') a
ORDER BY ID, GroupID;
see it working live here

Mysql query to get max age by section and if two or more has same age return student with smallest id

I have a table of students with temporary test values like this:
Table students
+----+-------------+-------+-----------+
| id | section_id | age | name |
+----+-------------+-------+-----------+
| 1 | 1 | 18 | Justin |
+----+-------------+-------+-----------+
| 2 | 2 | 14 | Jillian |
+----+-------------+-------+-----------+
| 3 | 2 | 16 | Cherry |
+----+-------------+-------+-----------+
| 4 | 3 | 19 | Ronald |
+----+-------------+-------+-----------+
| 5 | 3 | 21 | Marie |
+----+-------------+-------+-----------+
| 6 | 3 | 21 | Arthur |
+----+-------------+-------+-----------+
I want to query the table such that I want to get all the maximum ages of each section. However, if two students have the same age, the table produced will return the student with smallest id.
Return:
+----+------------+-----+--------+
| id | section_id | age | name |
+----+------------+-----+--------+
| 1 | 1 | 18 | Justin |
+----+------------+-----+--------+
| 3 | 2 | 16 | Cherry |
+----+------------+-----+--------+
| 5 | 3 | 21 | Marie |
+----+------------+-----+--------+
I tried this query:
SELECT ANY_VALUE(id), ANY_VALUE(section_id), MAX(age), ANY_VALUE(name) FROM
(SELECT id, section_id, age, name FROM students ORDER BY id) as X
GROUP BY section_id
Unfortunately, there are instances that id does not match the age and name.
I have on my end:
sql_mode = only_full_group_by
and I don't have a privilege to edit that, hence the any_value function but I have no idea how to use it.
This will do what you want.
It starts by finding the maximum age per section (including duplicates).
Then it joins those results with the minimum id per section (to eliminate duplicates).
And finally, select all fields for the matching id and section combinations.
SELECT s3.*
FROM students s3
INNER JOIN (
SELECT MIN(s2.id) AS id, s2.section_id
FROM students s2
INNER JOIN (
SELECT s1.section_id, MAX(s1.age) AS age
FROM students s1
GROUP BY s1.section_id
) s1 USING (section_id, age)
GROUP BY s2.section_id
) s2 USING (id, section_id);
Working SQL fiddle: https://www.db-fiddle.com/f/aezgAYM6A5KnXykceB7At1/0
I would simply use a correlated subquery:
select s.*
from students s
where s.id = (select s2.id
from students s2
where s2.section_id = s.section_id
order by s2.age desc, s2.id asc
limit 1
);
This is pretty much the simplest way to express the logic. And with an index on students(section, age, id), it should be the most performant as well.

An interesting SQL query CHALLENGE - list the AVERAGE value of the top 3 scores of each athlete

An interesting SQL query CHALLENGE:
A table named athelets consisting of id, ath_id, name, score, date.
+----+--------+-----------------+--------+------------+
| id | ath_id | name | record | r_date |
+----+--------+-----------------+--------+------------+
| 1 | 2 | John Wayne | 79 | 2010-07-08 |
| 2 | 7 | Ronald Regan | 51 | 2000-03-22 |
| 3 | 1 | Ford Harrison | 85 | 2009-11-13 |
| 4 | 2 | John Wayne | 69 | 2017-01-01 |
Please write a sql query to list the average value of the top three scores of each athlete, something like:
ath_id: 1, the arithmetic mean of his/her top 3 records: 77
ath_id: 2, the arithmetic mean of his/her top 3 records: 73
ath_id: 3, the arithmetic mean of his/her top 3 records: 47
select ath_id, avg(record)
from
(select ath_id, record
from atheletes as t1
where
(select count(*) from atheletes where t1.ath_id=ath_id and record > t1.record) < 3) as d
group by ath_id;
The above query should works as expected.
Assuming combinations of athletes and records are unique...
SELECT ath_id
, ROUND(AVG(record),2) top3_avg
FROM
( SELECT x.*
FROM athletes x
JOIN athletes y
ON y.ath_id = x.ath_id
AND y.record >= x.record
GROUP
BY x.id
HAVING COUNT(*) <=3
) a
GROUP
BY ath_id;