Неэффективный SQL-запрос

В настоящий момент я создаю простое веб-приложение, которое в один прекрасный день открою с открытым исходным кодом. В настоящий момент навигатор генерируется при каждой загрузке страницы (которая будет меняться для кэширования в один прекрасный день), но на данный момент это делается с помощью кода ниже. Используя PHP 5.2.6 и MySQLi 5.0.7.7, насколько эффективнее может быть код ниже? Я думаю, что помощь может помочь, но я советую. Любые советы будут очень признательны.

<?php
    $navQuery = $mysqli->query("SELECT id,slug,name FROM categories WHERE live=1 ORDER BY name ASC") or die(mysqli_error($mysqli));
    while($nav = $navQuery->fetch_object()) {
        echo '<li>';
            echo '<a href="/'. $nav->slug .'">'. $nav->name .'</a>';
            echo '<ul>';
                $subNavQuery = $mysqli->query("SELECT id,name FROM snippets WHERE category='$nav->id' ORDER BY name ASC") or die(mysqli_error($mysqli));
                while($subNav = $subNavQuery->fetch_object()) {
                    echo '<li>';
                        echo '<a href="/'. $nav->slug .'/'. $subNav->name .'">'. $subNav->name .'</a>';
                    echo '</li>';
                }
            echo '</ul>';
        echo '</li>';
    }
?>

Ответы

Ответ 1

Вы можете запустить этот запрос:

SELECT c.id AS cid, c.slug AS cslug, c.name AS cname,
    s.id AS sid, s.name AS sname
FROM categories AS c
    LEFT JOIN snippets AS s ON s.category = c.id
WHERE c.live=1
ORDER BY c.name, s.name

Затем повторите результаты, чтобы создать правильный заголовок, например:

// last category ID
$lastcid = 0;
while ($r = $navQuery->fetch_object ()) {

    if ($r->cid != $lastcid) {
        // new category

        // let close the last open category (if any)
        if ($lastcid)
            printf ('</li></ul>');

        // save current category
        $lastcid = $r->cid;

        // display category
        printf ('<li><a href="/%s">%s</a>', $r->cslug, $r->cname);

        // display first snippet
        printf ('<li><a href="/%s/%s">%s</a></li>', $r->cslug, $r->sname, $r->sname);

    } else {

        // category already processed, just display snippet

        // display snippet
        printf ('<li><a href="/%s/%s">%s</a></a>', $r->cslug, $r->sname, $r->sname);
    }
}

// let close the last open category (if any)
if ($lastcid)
    printf ('</li></ul>');

Обратите внимание, что я использовал printf, но вместо этого вы должны использовать свою собственную функцию, которая обертывается вокруг printf, но запускает htmlspecialchars через параметры (кроме первого, конечно).

Отказ от ответственности: я не обязательно поощряю такое использование <ul> s.

Этот код только здесь, чтобы показать основную идею обработки иерархических данных, полученных с одним запросом.

Ответ 2

Во-первых, вы не должны запрашивать свою базу данных в своем представлении. Это будет смешивать вашу бизнес-логику и логику представления. Просто присвойте результаты запроса переменной в контроллере и проведите через нее.

Что касается запроса, то yup-соединение может сделать это в 1 запросе.

SELECT * -- Make sure you only select the fields you want. Might need to use aliases to avoid conflict
FROM snippets S LEFT JOIN categiries C ON S.category = C.id
WHERE live = 1
ORDER BY S.category, C.name

Это даст вам начальный набор результатов. Но это не даст вам данные, упорядоченные так, как вы ожидаете. Вам нужно будет использовать немного PHP, чтобы сгруппировать его в некоторые массивы, которые вы можете использовать в своих циклах.

Что-то вдоль линий

$categories = array();
foreach ($results as $result) {
   $snippet = array();
   //assign all the snippet related data into this var

  if (isset($categories[$result['snippets.category']])) {

    $categories[$result['snippets.category']]['snippet'][] = $snippet;
  } else {
    $category = array();
    //assign all the category related data into this var;

    $categories[$result['snippets.category']]['snippet']  = array($snippet);
    $categories[$result['snippets.category']]['category'] = $category;
  }
}

Это должно дать вам массив категорий, которые имеют все связанные фрагменты в массиве. Вы можете просто пропустить этот массив, чтобы воспроизвести список.

Ответ 3

Я бы попробовал следующее:

SELECT
    c.slug,c.name,s.name
FROM
    categories c
LEFT JOIN snippets s
    ON s.category = c.id 
WHERE live=1 ORDER BY c.name, s.name

Я не тестировал его. Также проверьте индексы с помощью инструкции EXPLAIN, поэтому MySQL не выполняет полного сканирования таблицы.

С помощью этих результатов вы можете зацикливать результаты на PHP и проверить, когда изменится название категории, и построить свой вывод по своему усмотрению.

Ответ 4

Помимо одного комбинированного запроса вы можете использовать два отдельных.

У вас есть базовая древовидная структура с элементами ветвления (таблица категорий) и элементами листа (таблица фрагментов). Недостатком решения с одним запросом является то, что вы повторно получаете элемент brach-элемента владельца для каждого элемента листа. Это избыточная информация, и в зависимости от количества листов и объема информации, которую вы запрашиваете от каждого элемента ветвления, может возникать большой объем дополнительного трафика.

Решение с двумя запросами выглядит так:

$navQuery = $mysqli->query ("SELECT id, slug, name FROM categories WHERE live=1 ORDER BY name")
    or die (mysqli_error ($mysqli));
$subNavQuery = $mysqli->query ("SELECT c.id AS cid, s.id, s.name FROM categories AS c LEFT JOIN snippets AS s ON s.category=c.id WHERE c.live=1 ORDER BY c.name, s.name")
    or die (mysqli_error ($mysqli));

$sub = $subNavQuery->fetch_object ();    // pre-reading one record
while ($nav = $navQuery->fetch_object ()) {

    echo '<li>';
    echo '<a href="/'. $nav->slug .'">'. $nav->name .'</a>';
    echo '<ul>';

    while ($sub->cid == $nav->id) {

        echo '<li>';
        echo '<a href="/'. $nav->slug .'/'. $sub->name .'">'. $sub->name .'</a>';
        echo '</li>';

        $sub = $subNavQuery->fetch_object ();
    } 

    echo '</ul>';
}

Ответ 5

Он должен полностью напечатать тот же код, что и ваш пример

$navQuery = $mysqli->query("SELECT t1.id AS cat_id,t1.slug,t1.name AS cat_name,t2.id,t2.name
    FROM categories AS t1
    LEFT JOIN snippets AS t2 ON t1.id = t2.category
    WHERE t1.live=1
    ORDER BY t1.name ASC, t2.name ASC") or die(mysqli_error($mysqli));

$current = false;

while($nav = $navQuery->fetch_object()) {
    if ($current != $nav->cat_id) {
        if ($current) echo '</ul>';
        echo '<a href="/'. $nav->slug .'">'. $nav->cat_name .'</a><ul>';
        $current = $nav->cat_id;
    }

    if ($nav->id) { //check for empty category
        echo '<li><a href="/'. $nav->slug .'/'. $nav->name .'">'. $nav->name .'</a></li>';
    }
}

//last category
if ($current) echo '</ul>';