Decision
From your sample queries, I was able to get a simplification of your table structure, which provided me with enough information to answer your question. You can use this solution:
SELECT a.user_id, a.name, b.category_id, SUM(IF(d.plus = '1', d.points_amount, 0)) - SUM(IF(d.plus = '0', d.points_amount, 0)) AS points FROM Users a JOIN points_awarded b ON a.user_id = b.user_id JOIN ( SELECT links_from, links_to FROM category_relations UNION SELECT links_from, links_from FROM category_relations ) c ON b.category_id = c.links_from JOIN points_awarded d ON c.links_to = d.category_id WHERE a.user_id = $user_id GROUP BY a.user_id, a.name, b.category_id ORDER BY points DESC LIMIT 50
Where $user_id is the parameter of the user identifier for the request.
Structure
Basically, a key component of this query is the way you select the category_relations table.
Since you want the sum of the points of subcategories (for each parent category) plus to correspond to their parent categories, we can bind vertically to the parent category through UNION and essentially make it a subcategory itself. Then it will be well reflected in GROUP BY and SUM .
Let's say we have some (simplified) data:
Users ------------------ user_id | name 433 | Zane
points_awarded ------------------ user_id | category_id | plus | points_amount 433 | 1 | 1 | 785 433 | 2 | 1 | 871 433 | 3 | 1 | 236 433 | 4 | 0 | 64 433 | 5 | 0 | 12 433 | 7 | 1 | 897 433 | 8 | 1 | 3 433 | 9 | 0 | 48 433 | 10 | 1 | 124 433 | 14 | 0 | 676
category_relations ------------------ links_from | links_to 1 | 2 1 | 3 1 | 4 5 | 8 5 | 9 7 | 10 7 | 14
To distinguish between a parent and a subcategory, the query makes a self-intersection join in the points_awarded table using two fields in the category_relations table.
If we just joined the category_relations table, as without UNION , our resulting join would look something like (simplified columns):
points_awarded β (links_from) category_relations β (links_to) points_awarded:
category_id | points_amount | category_id | points_amount 1 | 785 | 2 | 871 1 | 785 | 3 | 236 1 | 785 | 4 | 64 5 | 12 | 8 | 3 5 | 12 | 9 | 48 7 | 897 | 10 | 124 7 | 897 | 14 | 676
As you can see, the leftmost category_id is the parent category, and the rightmost category_id is the corresponding subcategory. We can easily group the second points_amount by the first category_id and SUM ...
But wait, we also need to include the parent categories' points_amount ! How can we get the first points_amount in the second points_amount ? It uses UNION .
Before we complete our self-intersection join, we will list the category_relations table to change it a bit:
SELECT links_from, links_to FROM category_relations UNION SELECT links_from, links_from FROM category_relations
Which then leads to:
category_relations (subselected) ------------------ links_from | links_to 1 | 1 <-- parent category 1 | 2 1 | 3 1 | 4 5 | 5 <-- parent category 5 | 8 5 | 9 7 | 7 <-- parent category 7 | 10 7 | 14
If we essentially made each parent category a subcategory itself. We use the result of this in our mix, which then should produce:
category_id | points_amount | category_id | points_amount 1 | 785 | 1 | 785 1 | 785 | 2 | 871 1 | 785 | 3 | 236 1 | 785 | 4 | 64 5 | 12 | 5 | 12 5 | 12 | 8 | 3 5 | 12 | 9 | 48 7 | 897 | 7 | 897 7 | 897 | 10 | 124 7 | 897 | 14 | 676
Here NOW the parent category is counted in SUM , and now we can group the first category_id by adding up the second points_amount (ignore the first points_amount on this because it does not matter), giving you the desired result.