Reputation: 1063
We have users, questions and unlimited levels of categories. The users can get some points from questions. Questions can have multiple categories.
What I want to do is to calculate the top users per category: It's simply total points taken from the questions under that category AND it's sub-categories too.
So, I have these tables:
questions
--------------
id
title
question
categories
--------------
id
parent_id
category
lft
rgt
question_categories
--------------
question_id
category_id
users
--------------
id
username
user_points
--------------
id
user_id
question_id
point_type
points
user_category
--------------
user_id
category_id
points
What I want to do is to calculate user_category.points value. Summing up the points for each category is easy but including the sub-categories is getting complicated.
What might be the best way to do this?
Example calculation:
Let's say the categories are:
Programming
PHP
Zend Framework
Symfony
Java
Ruby on Rails
Assume that the user got 3 points from Zend Framework, 2 points from PHP, 5 points from java and 1 point from Rails. The points for this user per categories will be:
Programming 11 (5+5+1)
PHP 5 (2+3)
Zend Framework 3
Symfony
Java 5
Ruby on Rails 1
Upvotes: 0
Views: 539
Reputation: 5846
This php and SQL should get you the top 3 users for each category including sub categories:
$query = "SELECT id, parent_id FROM categories";
$parent = array();
...fetch mysql data loop depending on what connection you use, mysqli or pdo...
{
$parent[$result['id']] = $result['parent_id'];
}
$childs = array();
foreach($parent as $id => $parrent_id)
{
$childs[$parrent_id][$id] = $id;
$next_parrent_id = $parrent_id;
while($next_parrent_id = $parent[$next_parrent_id])
{
$childs[$next_parrent_id][$id] = $id;
}
}
foreach($parent as $id => $parrent_id)
{
$current_categories = array($id => $id) + $childs[$id];
$query = "SELECT user_id, username, SUM(points) AS total_points
FROM user_points
LEFT JOIN users ON (user_id = users.id)
LEFT JOIN question_categories USING (question_id)
WHERE category_id IN (" . implode(', ', $current_categories). ")
ORDER BY total_points DESC
LIMIT 3";
...fetch mysql data loop...
}
Upvotes: 0
Reputation: 27311
If you're only going to sum per top-level category, then you should add a field to your categories table called root_id (holding the id of the transitive parent of the category).
Then your sum would be calculated as:
select up.user_id, ctg.root_id, sum(up.points)
from user_points up
join question_categories qc on up.question_id = qc.question_id
join categories ctg on qc.category_id = ctg.id
group by up.user_id, ctg.root_id
Upvotes: 0
Reputation: 1158
Perhaps it would be best to use tags instead of a hierarchy. For instance, anything with a "Zend Framework" will also have "PHP" and "Programming" tags. This also helps when some categories can appear in multiple places. For instance, I can use ajax in jQuery and also Javascript. Then, add 1 to each tag listed in the category for the user.
Upvotes: 1
Reputation: 53525
I would create a user_categories
table in which I would store 3 values: user_id
, category_id
and user_score
. It's easy to maintain (need only to INSERT
or UPDATE
) and it's also easy to query for top-users of every category.
Upvotes: 0