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.