I have a MySQL table called category that holds hierarchical data as an adjacent list. I then echo out the list of all categories and their children as a html list:
Food
Fruit
Red
Cherry
Yellow
Banana
Meat
Beef
Pork
Sports
Soccer
Spanish Soccer
French Soccer
Golf
US Open
Tiger Woods
The code i am using for that is:
$refs = array();
$list = array();
$sql = "SELECT catid, parentid, name FROM category ORDER BY name";
$result = mysql_query($sql);
while($data = mysql_fetch_assoc($result)) {
$thisref = &$refs[ $data['catid'] ];
$thisref['parentid'] = $data['parentid'];
$thisref['name'] = $data['name'];
$thisref['catid'] = $data['catid'];
if ($data['parentid'] == 0) {
$list[ $data['catid'] ] = &$thisref;
} else {
$refs[ $data['parentid'] ]['children'][ $data['catid'] ] = &$thisref;
}
}
function toUL($arr){
$html = '<ul>';
foreach ($arr as $v){
$html .= '<li><a href="category.php?id=' . $v['catid'] . '">' . $v['name'] . '</a>';
if (array_key_exists('children', $v)){
$html .= toUL($v['children']);
}
$html .= '</li>';
}
$html .= '</ul>';
return $html;
}
// build the list and output it
echo toUL($list);
The MySQL table “category” has rows called: catid, name, description, parentid, and level.
The issue is that I want that table to only show the categories that a user likes. Such as:
Food
Fruit
Yellow
Banana
Meat
Beef
Sports
Soccer
Spanish Soccer
French Soccer
Tiger Woods
The "likes" has rows called: likeid, userid, catid where the userid references the primary key of the "users" table and the userid references the primary key of the "category" table.
What sort of code could i implement to create an individual list of topics that a user likes? The issue i see, and do not currently have an answer for is: what if a user likes "Tiger Woods" without liking the parent topic of "Golf"?
How could I output a list of just the categories a user may like?
First you need to join all matching likes of a user together with its category, then select catid, parentid and name of the category part of the join.
Note that you need to use define which “catid” you want to select out of it, else mysql will give you an error message (It cant decide between catid from the category or likes table of the join; we know it’s not relevant, because it has the same value, but mysql doesn’t ‘realize’ that). I gave category the alias “c” and likes the alias “l”, so you can write c.catid instead of category.catid.
Regarding the 2nd problem: If the user doesn’t like one of the parent categories. You should display the parent categories even he doesn’t like them, but differently (as different colour). But you at least need all parent nodes, as you need to know where to attach the liked node. Without the intermediate nodes you won’t be able to acomplish that.
The easiest way is to select an ‘outer’ joined result of the tables ‘categories’ and ‘likes’; ‘outer join’ means that if for one table the other joined information isn’t available (e.g. category row with no like row for that user), then this row of the table (e.g. category) is also added to the result but the part of the unavailable table (here ‘likes’) is set to NULL.
To be more exakt: we want a “categories LEFT OUTER JOIN likes”, which means: categories (=LEFT) which have or haven’t likes, but no likes that have no category. This should’nt happen. But if, for any reason, you delete a category but don’t delete the likes for it, you’d get into trouble…
The matching criteria is the catid column, therefore we write “ON c.catid=l.catid” (or “ON categories.catid = likes.catid”, if you don’t want to use the name aliases… as explained below).
Let’s have a look at the result of the JOIN for this SQL command:
Now we get a table of all categories with added matching columns from the “like” table, if available… or set to NULL, if unavailable:
So, what we need is c.catid AS catid, c.parentid AS parentid, c.name AS name (note that we can rename the column names with “AS newname”, so we don’t need to change our program).
Additionally we need “(l.catid IS NOT NULL) AS liked”, which gives us “true” (value: “1”) for all l.catid with != NULL and “false” (value: “0”) if l.catid == NULL.
In our SQL command that looks like this:
Ok, now we implement the SQL-result-fetching as before. But a bit modified because of the new “liked” field in the result. And we mark add a “mark”-value to each node, which is initially set for each “liked” node. Later we will also mark all parents of “mark”ed nodes. These are the nodes we want to display…
Now you recursively mark the part of the tree, that needs to be displayed. Therefore we start at the root of the graph, as for the toUL() function.
As we want to display any node that has child nodes, that need to be displayed (=is liked by user), we analyze the childs subtree first. If at the end, one of the subtrees has nodes that we need to display OR that node itself needs to be displayed (is liked), we mark it. It’s not much differently from how toUL() works… but the traversal order is different (“childs first, then current node” vs “current node, then childs”). Here’s the code:
Now we need to modify the toUL()-function to only display nodes that have $v[“mark”] == true like this:
And finally we call the function that marks all parent nodes of liked nodes and call our new toUL()-function:
Additionally, you can gray out all nodes (=display them differently) that have $v[‘mark’] set but not $v[‘like’]… Or you just add some “is-liked” symbol behind the categories name, if it is liked.
This could be like this: