Select a Sum the last 5 rows - mysql

I'm building a little quiz game in PHP/MySQL. After asking questions I want a screen to display how many of the last round were answered correctly.
Im storing whether they were answered correctly or not in a table that looks like this:
rowID | questionid | playerid | answercorrect |
1 | 1 | 1 | 1 |
2 | 2 | 1 | 1 |
3 | 3 | 1 | 1 |
4 | 4 | 1 | 1 |
5 | 5 | 1 | 0 |
6 | 6 | 1 | 1 |
7 | 7 | 1 | 1 |
I want to see how many of the last x (usually 5) questions were answered correctly.
I thought this would be simple. I'm trying this:
SELECT sum( answercorrect ) FROM `answersgiven` ORDER BY id DESC LIMIT 5
I thought this would sum the answercorrect column for the last 5 rows, giving me an answer of 4, but it's giving me 7, which is the result for ALL of the rows.
I feel like I'm missing something obvious. It seems like a simple thing to want to do.
Any ideas?

Try this:
SELECT sum(answercorrect)
FROM (SELECT answercorrect FROM `answersgiven` ORDER BY rowID DESC LIMIT 5) t1
Example Fiddle
In your query, the LIMIT clause affects the overall result: So first all are summed up (which results in one row - the sum over all rows) and then the first 5, if available, are taken (which again is just the one row).
The easiest way of achieving your target is to first select just the first 5 rows (in the subselect) and then sum up afterwards.

try this query
SELECT sum(col)
FROM (SELECT col FROM `table` ORDER BY id DESC LIMIT 5) t1

You're almost there. Just sum up those top 5 answers now:
SELECT SUM('top5')
FROM
(SELECT answercorrect AS 'top5'
FROM `answersgiven`
ORDER BY id DESC LIMIT 5) 'x'

Related

Conditionally mixing ORDER BY

I have a MySQL table where I want part of it sorted differently. For instance, I want the following result set:
+----+
| id |
+----+
| 1 | #ascending
| 2 |
| 3 |
| 4 |
| 8 | #descending
| 7 |
| 6 |
| 5 |
+----+
I essentially want to do something like:
ORDER BY
WHEN id <= 4 THEN
id ASC
ELSE
id DESC
END;
which is of course nonsense. Is there a sensical equivalent? I’ve tried using UNION, but subquery order is ignored.
You can use a two stage ordering process; the first part orders by whether id is less than or equal to 4, and the second orders by either id (for id <= 4) or -id (for id > 4) so that values greater than 4 sort descending:
SELECT *
FROM data
ORDER BY id <= 4 DESC,
CASE WHEN id <= 4 THEN id ELSE -id END
Output:
id
1
2
3
4
8
7
6
5
Demo on SQLFiddle
Someone posted this answer but deleted it; I’m reposting because it’s still a valid answer.
Rather than working the order change into an ELSE clause, you can limit one rule to a subset and the next will apply to the rest:
ORDER BY id <= 4 DESC,
CASE WHEN id <= 4 THEN id END ASC,
id DESC;
Demo on SQLFiddle

Retrieve records using max and sum of time, if different users have same time, get the record with latest date created [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
Trying to retrieve records by first summing their time_spent, then using max to retrieve the largest record by time. which seems to be working.
I need to now check if on the chance that the sum of time_spent are the same value for users (a tie, like in the example below, both users have time_spent as 10 so it should then select the user that has the latest post), if they are then I need to only get the user_id that was posted last (newer) using the created_at column. I just don't know what to use to do that check, is it a CASE, or IF Function? and if so where would it go in my query?
Here is a sql fiddle link: http://sqlfiddle.com/#!9/f24985/2
Table1 layout
+----+---------+-----------+---------+------------+------------+
| id | user_id | member_id | item_id | time_spent | created_at |
+----+---------+-----------+---------+------------+------------+
| 1 | 1 | 1 | 1 | 5 | 2019-06-01 |
| 2 | 2 | 1 | 1 | 1 | 2019-06-07 |
| 3 | 2 | 1 | 1 | 5 | 2019-06-08 |
| 4 | 2 | 1 | 2 | 4 | 2019-06-01 |
| 5 | 1 | 1 | 2 | 5 | 2019-06-07 |
+----+---------+-----------+---------+------------+------------+
Current SQL:
SELECT
MAX(attribute_time.sum_time), attribute_time.user_id
FROM (
SELECT
SUM(time_spent) AS sum_time, user_id
FROM
table1
WHERE
member_id = 1
AND item_id IN (1, 2)
AND (created_at BETWEEN '2019-06-1' AND '2019-06-30')
GROUP BY
user_id
ORDER BY
sum_time desc
) AS attribute_time;
In this example, both users have a total of 10 for time, currently returns the first record of the 2 and not based on the created_at date, which in this case, should be user 2.
Expected
+---------+
| user_id |
+---------+
| 2 |
+---------+
This is what you are looking for. http://sqlfiddle.com/#!9/a5306c/4/0
The MAX clause is problematic for sub-queries involving quantities unless you use some repetitive and verbose queries (DRY!), as seen in answer here: MySQL: Select MAX() from sub-query with COUNT() - it seems to decouple the rows, so you get the highest (max) sum_time with the wrong id (I thought I was seeing things, seemed so simple)
I used LIMIT to get around it. Sorting descending (the highest on top), and then LIMITing the result to 1 achieves the same thing as "Max".
Also - Im not sure if in event of a tie in max time you wanted to pick the earliest or latest record, but this picks the latest. I use MAX to pick the last day/time for each user, and orderby sum_of_time, then by date. If you want the opposite, sub MIN for MAX and/or DESC for ASC in the order-by. Regards! Thx for the exercise.
SELECT
SUM(time_spent) AS sum_time, user_id, MAX(created_at)
FROM
Table1
WHERE
member_id = 1
AND item_id IN (1, 2)
AND (created_at BETWEEN '2019-06-1' AND '2019-06-30')
GROUP BY
user_id
ORDER BY
sum_time DESC, created_at DESC
LIMIT 1
Try to use this it will give you user id 2
SELECT
MAX(attribute_time.sum_time), attribute_time.user_id
FROM (
SELECT
SUM(time_spent) AS sum_time, user_id
FROM
table1
WHERE
member_id = 1
AND item_id IN (1, 2)
AND (created_at BETWEEN '2019-06-1' AND '2019-06-30')
GROUP BY
user_id
ORDER BY
sum_time,user_id desc
) AS attribute_time;

Firstly do Group By then retrieve all rows as per order?

I have table where I have id and time.
ID | Time
1 | 8.35
1 | 8.40
3 | 8.43
4 | 8.45
1 | 8.50
2 | 8.52
3 | 8.54
4 | 8.55
1 | 8.57
2 | 9.01
3 | 9.05
5 | 9.06
Required Result
ID | Time
5 | 9.06
3 | 9.05
3 | 8.54
3 | 8.43
2 | 9.01
2 | 8.52
1 | 8.57
1 | 8.50
1 | 8.40
1 | 8.35
4 | 8.55
4 | 8.45
Currently I am doing it by Select * from table group by ID order by Time DESC and get
Result One:
ID | Time
5 | 9.06
3 | 9.05
2 | 9.01
1 | 8.57
4 | 8.55
then writing second query and storing data in list.
foreach value in Result one:
Select * from Table where ID = value
Instead of writing a loop, I will like to have only one query.
Basic problem is I want to group IDs and top group should be the item that occured recently. As in example 1 occurs many time but I will consider only the latest time while grouping.
Can I write only one query to get result?
SELECT ID, Time FROM Table ORDER BY ID, Time
Grouping combines matching rows, so you do not want to group, ordering puts them in order, and that's what you want, you want all the IDs in order then all the times in order within those ids, so you want to order by ID then order by time.
UPDATE due to question edit
This can be done with a join to a sub select
SELECT t.ID. t.Time FROM Table t
JOIN (SELECT ID, Max(Time) as Time FROM Table GROUP BY ID) ss
ON t.ID = ss.ID
ORDER BY ss.Time DESC, t.ID DESC, t.Time DESC
The sub select (ss) does the first query you have there, and joins it to the main table, letting you order by the highest(max) time for each ID, then by the ID and the Time for the row itself. Note that all the ordering is done on the final query, ordering in the sub select is useless, since the join will reorder it anyways.

How do I combine two queries on the same table to get a single result set in MySQL

I am not very good at sql but I am getting there. I have searched stackoverflow but I can't seem to find the solution and I hope someone out there can help me. I have a table (users) with data like the following. The book_id column is a key to another table that contains a book the user is subscribed to.
|--------|---------------------|------------------|
| id | book_id | name |
|--------|---------------------|------------------|
| 1 | 1 | jim |
| 2 | 1 | joyce |
| 3 | 1 | mike |
| 4 | 1 | eleven |
| 5 | 2 | max |
| 6 | 2 | dustin |
| 7 | 2 | lucas |
|--------|---------------------|------------------|
I have a function in my PHP code that returns two random users from a specific book id (either 1 or 2). Query one returns the result in column 1 and result two returns the results in column 2 like:
|---------------------|------------------|
| 1 | 2 |
|---------------------|------------------|
| jim | max |
| joyce | dustin |
|---------------------|------------------|
I have achieved this by running two separate queries as seen below. I want to know if it's possible to achieve this functionality with one query and how.
$random_users_with_book_id_1 = SELECT name FROM users WHERE book_id=1 LIMIT 2
$random_users_with_book_id_2 = SELECT name FROM users WHERE book_id=2 LIMIT 2
Again, I apologise if it's too specific. The query below has been closest to what I was trying to achieve.:
SELECT a.name AS book_id_1, b.name AS book_id_2
FROM users a, users b
WHERE a.book_id=1 AND b.book_id = 2
LIMIT 2
EDIT: I have created a fiddle to play around with his. I appreciate any help! Thank you!! http://sqlfiddle.com/#!9/7fcbca/1
It is easy actually :)
you can use UNION like this:
SELECT * FROM (
(SELECT * FROM user WHERE n_id=1 LIMIT 2)
UNION
(SELECT * FROM user WHERE n_id=2 LIMIT 2))
collection;
if you read this article about the documentation you can use the () to group the individual queries and the apply the union in the middle. Without the parenthesis it would still LIMIT 2 and show only the two first. Ref. "To apply ORDER BY or LIMIT to an individual SELECT, place the clause inside the parentheses that enclose the SELECT:"
If you want to combine the queries in MySQL, you can just use parentheses:
(SELECT name
FROM users
WHERE n_id = 1
LIMIT 2
) UNION ALL
(SELECT name
FROM users
WHERE n_id = 2
LIMIT 2
);
First, only use UNION if you specifically want to incur the overhead of removing duplicates. Otherwise, use UNION ALL.
Second, this does not return random rows. This returns arbitrary rows. In many cases, this might be two rows near the beginning of the data. If you want random rows, then use ORDER BY rand():
(SELECT name
FROM users
WHERE n_id = 1
ORDER by rand()
LIMIT 2
) UNION ALL
(SELECT name
FROM users
WHERE n_id = 2
ORDER BY rand()
LIMIT 2
);
There are other methods that are more efficient, but this should be fine for up to a few thousand rows.

Retrieve distinct values without reducing number of results

I'm writing a MySQL request for retrieving data from a list of questions.
The table looks like this :
-----------------------------------------------------
| id | answer_name | rating | question_id | answers |
-----------------------------------------------------
Where several rows can have the same answer_name value, since several questions can be asked about the same answer.
Now, for retrieving the data I use a LIMIT clause which is calculated from ratings and the total number of rows.
For example, if I wanna get the data between 80% and 100% of rating, and there are 100 rows, I would use ORDER BY rating LIMIT 80, 20.
My problem is the following : I need to retrieve data with distinct values for answer_name column, but using a GROUP BY clause makes the number of result (e.g. of rows in the table) reduce cause of aggregation, causing the top percentages of rows to return nothing cause of searching rows at a limit that doesn't exist.
Does anyone know if there is a way to keep the number of results the same and still to retrieve distinct results for the answer_name column ?
EDIT :
Here are some sample rows and expected output :
game_data table :
-----------------------------------------------------
| id | answer_name | rating | question_id | answers |
|----|-------------|--------|-------------|---------|
| 1 | A. Merkel | 40 | 1 | [1,2,3] |
| 2 | A. Merkel | 45 | 2 | [2,3,4] |
| 3 | B. Clinton | 55 | 1 | [2,5,8] |
| 4 | B. Clinton | 50 | 2 | [3,5,8] |
| 5 | L. Messi | 17 | 4 | [7,8,9] |
| 6 | L. Messi | 18 | 5 | [7,8,9] |
| 7 | L. Messi | 25 | 6 | [7,8,9] |
| 8 | D. Beckham | 21 | 4 | [6,7,8] |
| 9 | D. Beckham | 52 | 5 | [6,7,8] |
| 10 | D. Beckham | 41 | 6 | [6,7,8] |
-----------------------------------------------------
Where answers is an array of ids referring to another table.
Let's say I wanna retrieve the 50% to 80% of the table, ordered by rating.
SELECT id FROM game_data GROUP BY answer_name ORDER BY rating LIMIT 5, 3
Here the problem is the GROUP BY answer_name is gonna reduce the number of rows of the table, and therefore instead of returning 3 results, will return an empty set.
Also, I want the selected value in the GROUP BY close to be randomly chosen.
Using group by like this goes against pretty much every instinct, but you said you want random values, so it's good enough.
select * from (
select q.*, #rank := #rank + 1 as rank
from (
select * from game_data
group by answer_name
order by rating desc
) q, (select #rank := 0) qq
) qqq
where rank between (#rank * .5) and (#rank * .8)
demo here
How does it work? First (in the innermost query) we group by your answer_name, to get your distinct results, and we order it by the rating as required.
Then in the query wrapping around that one, we give those results a ranking from 1 to however many rows are in the result. Once this level of the query completes, we know our best answer is answer 1, and our 'worst' answer is the last value of our #rank variable.
Then we get to the outermost query. We can use that #rank variable to determine our percentages, which we use to filter the where clause.
In all likelihood this will give you the same results each time you run the same query, but the values chosen are indeterminate - so it could change. If you want truly random (ie changes with each execution) that's a different kettle of fish altogether.
(note, this bit: , (select #rank := 0) qq is purely to initialise the variable)
Simple is That.
Use Group By 'id' not 'answer_name' b/c Group By not get duplicate values
SELECT * FROM game_data GROUP BY id ORDER BY rating