使用单个查询获取类别和文章

I got two SQL-Tables:

  1. categories: id, title, level, parent
  2. articles: id, title, category_id

Now I want to get this result:

Category 1
    Article 1
    Artcile 2
    Article 3
Category 2
    Subcategory 1
        Article 1
        Artcile 2
        Article 3
    Subcategory 2
        Article 1
        Artcile 2
        Article 3
Category 3
    Article 1
    Artcile 2
    Article 3

I would do it this way:

$categories = $db->prepare('SELECT * FROM categories WHERE level = 0');
$categories->execute();
while($category = $categories->fetch(PDO::FETCH_OBJ)) {
    $sub_categories = $db->prepare('SELECT * FROM categories WHERE parent = :parent_id');
    $sub_categories->bindParam(':parent_id', $category->id, PDO::PARAM_INT);
    $sub_categories->execute();
    while($sub_category = $sub_categories->fetch(PDO::FETCH_OBJ)) {
        $articles = $db->prepare('SELECT * FROM articles WHERE category_id = :category_id');
        $articles->bindParam(':category_id', $sub_category->id, PDO::PARAM_INT);
        $articles->execute();
        while($article = $articles->fetch(PDO::FETCH_OBJ)) {
            echo $article->title.'<br />';
        }
    }

}

First of all, this is quite bad, as it just gets articles from the sub-categories (not for the categories) and there is just the selection of one level deep.

Second: I'm using three querys, which are executed multiple times, just for that basic thing. Performance... :-(

I would like to solve that problem with just one SQL-query. Is this possible? (Then I would create the output...)

SQLfiddle: http://sqlfiddle.com/#!2/2a4f2

You could try this statement:

SELECT
    A.id, A.title,
    C.id AS category_id, C.title AS category_title, C.level AS category_level,
    P.id AS parent_id, P.title AS parent_title, P.level AS parent_level
FROM articles AS A
    INNER JOIN categories AS C ON A.category_id=C.id
    LEFT JOIN categories AS P ON C.parent=P.id

This gives you all articles including primary category and (optional) parent category. But you have to consider, that category can be the main or the sub category.

You can change it that way to always get the main and sub category directly and sort for it.

SELECT
    IFNULL(P.id, C.id) AS category_id, IFNULL(P.title, C.title) AS category_title,
    IF(P.id IS NULL, NULL, C.id) AS subcategory_id, IF(P.title IS NULL, NULL, C.title) AS subcategory_title,
    A.id, A.title
FROM articles AS A
    INNER JOIN categories AS C ON A.category_id=C.id
    LEFT JOIN categories AS P ON C.parent=P.id
ORDER BY category_id, subcategory_id, A.id

The IFNULL looks if there is a parent category. If yes, the parent P is main category; if not, the category C is the main. The next line checks is parent P is NULL. If yes, there is no subcategory; if no, the Category C is the subcategory. Looks a little unhandy, but for two levels it works fine:

http://sqlfiddle.com/#!2/0ed4d/4