Неэффективный SQL-запрос - PullRequest
14 голосов
/ 03 апреля 2011

Я создаю простое веб-приложение в тот момент, когда в один прекрасный день у меня будет открытый исходный код. В настоящий момент навигация генерируется при каждой загрузке страницы (которая будет изменена для кэширования через день), но на данный момент она выполняется с помощью приведенного ниже кода. Используя 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>';
    }
?>

Ответы [ 5 ]

15 голосов
/ 03 апреля 2011

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

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's 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's close the last open category (if any)
if ($lastcid)
    printf ('</li></ul>');

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

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

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

3 голосов
/ 03 апреля 2011

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

Что касается запроса, то объединение может сделать это за 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;
  }
}

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

1 голос
/ 06 апреля 2011

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

Здесь у вас есть базовая древовидная структура с элементами ветви (таблица категорий) и листовыми элементами (таблица фрагментов). Недостатком решения с одним запросом является то, что вы получаете элемент 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>';
}
1 голос
/ 03 апреля 2011

Я бы попробовал это:

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 и проверить, когда имя категории изменится, и построить свой вывод по своему желанию.

0 голосов
/ 06 апреля 2011

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

$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>';
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...