How to combine several tables in a mysql query and rank based on total of a column

Viewed 209

I have several tables I need help to generate a query string. I am currently querying each of the tables and combining the results in the frontend which is making too many requests to the backend

Below is the structure of my tables (See this Sample sqlfiddle )

users

id first_name last_name
1 John Doe
2 Peter Parker

events

id name
1 New Year wake contest
2 Bright Monday contest

question

id description points
1 Some Question Here 2
2 Another Question Here 4.5

A question can belong to many events while events can have many questions

event_question

question_id event_id
1 2
1 1

Answers belong to a question

answers

id description question_id is_correct
1 Answer is 4 1 1
2 Not True 1 0

The final step is to save the info about an exam attempt

attempts

user_id attempt question_id answer_id event_id
2 1 1 1 2
2 1 2 8 2

I need help with coming up with a query that will retrieve the rank of a specific contest.

Lets say event_id = 1

Expected output

For event with id = 1 the rank will be

user_id first_name last_name points
5 Paul Morphy 13.5
4 Bobby Fisher 9.5
1 John Doe 9.5
2 Peter Parker 6
3 Alexander Alekhine 6

For event with id = 2 the rank will be

user_id first_name last_name points
4 Bobby Fisher 7
1 John Doe 6
2 Peter Parker 6
5 Paul Morphy 5
3 Alexander Alekhine 3

points is the sum of points for the questions that a given user provided correctly

I am using PHP (Laravel) and MySql. A solution with Laravel eloquent or simply an SQL query are both welcomed

3 Answers

I guess each id column in each of your tables is unique, that is each table has no repeated id values. That's definitely the case in your fiddle.

And, I guess no user makes repeated attempts to answer a particular question in a particular event.

Here's a pure SQL query to get what you want. (https://www.db-fiddle.com/f/kRKsYAg954msn7bebtgUpg/2)

SELECT question_attempts.event_id,
       users.id user_id, users.first_name, users.last_name,
       SUM(questions.points) points
  FROM question_attempts
  JOIN questions   ON question_attempts.question_id = questions.id
  JOIN answers     ON question_attempts.answer_id = answers.id
  JOIN users       ON question_attempts.user_id = users.id
 WHERE answers.is_correct = 1
 GROUP BY question_attempts.event_id,
         users.id, users.first_name, users.last_name

Why does this work? Your question_attempts table ties all the other tables together.

  • JOIN questions ON question_attempts.question_id = questions.id pulls in the question for each answer attempt. We need that because it says how many points the question is worth.
  • JOIN answers ON question_attempts.answer_id = answers.id pulls in the answer for each attempt. We need that because we need to know whether the particular answer is_correct.
  • JOIN users ON question_attempts.user_id = users.id gets us the users' names.

Then, the SUM / GROUP BY adds up the points for correct answer for each event and user.

This query gets back all your events and users. You can add

WHERE ...  AND question_attempts.event_id = 6

or some such thing to get the results for just one event.

And you can use

ORDER BY question_attempts.event_id,
         SUM(questions.points) DESC,
         users.id

to get your output in descending order of score (highest first) for each event.

This is example where triggers would come in handy. Use them to calculate Points directly into a table column. One you will have milions of rows and each time you gonna calculate this... over time it will get worst and worst.

Everytime you update answers you will update user row to update points.

Because your problem seems to be in houw to use ranking, I created an example on your questions table, and added a RANK on the points:

It is an example how to do RANKing (actually DENSE_RANK) in MySQL 5.6. When you are using MySQL 8.0 then you should user RANK or DENSE_RANK

SET @R:=0; 
SET @P:=0;
SELECT 
   id,
   description,
   points,
   CASE WHEN @P=points THEN @R ELSE @R:=@R+1 END as `RANK`,
   @P:=points
FROM questions
ORDER BY points DESC

For output, see fiddle, or:

id description points RANK @P:=points
5 What was the first disease to be discovered 3.5 1 3.5
10 A child has more bones than an adult 3 2 3
8 Average time to complete a Marathon is 2hrs 3 2 3
7 An earthworm can grow back when cut 2 3 2
6 The statement a swarm of butterfly is valid 2 3 2
4 Sudan is the largest country in Africa 2 3 2
3 A cat dies 9 times 2 3 2
2 Blood color changes 2 3 2
1 Water is blue 2 3 2
9 What is the value of one plus one divide two? 1 4 1
Related