Instead of doing it in a single query, I would suggest a solution of looping over separate, multiple queries.
For your case, only 3 separate queries are needed because you have 3-tier (Cat/Subcat/Article). On the other hand, since the number of tiers is already defined (and limited), there is no need to use recursion this time (if there are unlimited (or unknown) levels of hierarchy, we need recursion).
Please change the database schema to a proper, and clearly defined one (as follows):
category (catid, name)
subcategory (subcatid, catid, name)
article (articleid, catid, subcatid, name)
where catid is linking to category
; and subcatid is linking to subcategory
, if applicable
So the data will be (refering to the picture in your post)
category (catid, name)
1 Category1
2 Category2
3 Category3
subcategory (subcatid, catid, name)
1 2 Subcategory1
article (articleid, catid, subcatid, name)
1 0 0 Article6
2 1 0 Article1
3 2 1 Article7
4 2 1 Article8
5 2 0 Article2
6 2 0 Article3
7 3 0 Article4
8 3 0 Article5
I think the advantage and clarity is obvious in the above schema, so I don't think further explanation is needed. Of course you may add further data fields to the tables but the main structure is as above.
Now, please use PHP to generate the following HTML:
For 1st ul, li (just run once)
(a) loop over category and display the name
then
(b) loop over article display records having (catid=0 or catid is null) and (subcatid=0 or subcatid is null) , then display the name
2nd ul,li
For each of the records in (a) above:
(c) loop over subcategory which as catid=category.catid and display the subcategory name
then
(d) loop over article, display records having catid=category.catid and (subcatid=0 or subcatid is null) , then display the name
3rd ul,li
For each of the records in (c) above:
(e) loop over article, display records having catid=category.catid and subcatid=subcategory.subcatid , then display the name
To demonstrate, I will put the "2nd ul, li" as a function known as branch2(); and "3rd ul, li" as a function known as branch3(), and then call them accordingly.
So, the code will be:
<?php
$servername = "localhost";
$username = "xxxxxxx";
$password = "xxxxxxxxxx";
$dbname = "xxxxxx";
$conn = new mysqli($servername, $username, $password, $dbname);
$sql1="(
SELECT name, catid AS id1, '1' as subdata
FROM category
ORDER BY id1
)
UNION (
SELECT name, articleid AS id1, '0' as subdata
FROM article
WHERE catid =0
AND subcatid =0
ORDER BY id1
)";
$result = $conn -> query($sql1);
$index=0;
$count=mysqli_num_rows($result);
echo "<ul>";
while ($index <$count) {
$row = $result -> fetch_assoc() ;
echo "<li>". $row["name"];
if ($row["subdata"]=='1'){
branch2($row["id1"], $conn);
}
$index++;
}
echo "</ul>";
$result -> free_result();
function branch2($var1, $conn){
$sql2="(
SELECT name, catid AS id1, subcatid as id2, 1 as subdata
FROM subcategory where catid=". $var1 . " ORDER BY id1 )
UNION (
SELECT name, articleid AS id1, 0 as id2 , 0 as subdata
FROM article
WHERE catid =" . $var1 . " AND subcatid =0 ORDER BY id1 )";
$result2 = $conn -> query($sql2);
$index2=0;
$count2=mysqli_num_rows($result2);
echo "<ul>";
while ($index2 <$count2) {
$row2 = $result2 -> fetch_assoc() ;
echo "<li>". $row2["name"];
if ($row2["subdata"]=='1') {
branch3($row2["id1"], $row2["id2"], $conn);
}
$index2++;
}
echo "</ul>";
$result2 -> free_result();
}
function branch3($var1, $var2, $conn){
$sql3="(
SELECT name, articleid AS id1, 0 as id2 , 0 as subdata
FROM article
WHERE catid =" . $var1 . " and subcatid=". $var2 . " ORDER BY id1 )";
$result3 = $conn -> query($sql3);
$index3=0;
$count3=mysqli_num_rows($result3);
echo "<ul>";
while ($index3 <$count3) {
$row3 = $result3 -> fetch_assoc() ;
echo "<li>". $row3["name"];
$index3++;
}
echo "</ul>";
$result3 -> free_result();
}
?>
The fully working example can be seen here:
http://www.createchhk.com/SO/testso10Nov2021.php

- ; and (2) Instead of doing it in a single query, why not looping on **separate, multiple** queries ?
– Ken Lee Nov 09 '21 at 22:26