как грамотно выбрать дерево из БД?

Discussion in 'PHP' started by Дикс, 6 Feb 2009.

  1. Дикс

    Дикс Elder - Старейшина

    Joined:
    16 Apr 2006
    Messages:
    1,194
    Likes Received:
    227
    Reputations:
    26
    есть таблица с комментариями, у некоторых комментариев так называемый parent_id - "комментарии на комментарии".

    искал решение грамотной выборки без миллиона запросов к бд.

    нашёл вот это:
    http://habrahabr.ru/blogs/sql/43955/
    там огорчили, что в mysql нет нормальных средств для этого и типа пользуйтесь пхп, и ссылка вот сюда:

    http://www.codenet.ru/webmast/php/tree.php

    это понятно - нерациональный говнокод.

    собственная мысль (сонный мозг уже ниче не соображает):
    PHP:
    -select comments where parent_id == null
    $ids 
    все выбранные идчерез запятую
    -select comments where parent_id in($ids)

    фореач comments{
    если in_array(текущий ид$parent_ids)
       
    то выбираем к нему все комментарии второго уровня

    }
    в результате мы делаем тока два запроса (может можно и один сделать через иннер джоин?)
    и потом выводим средставми пхп в нужном порядке.
     
  2. .:EnoT:.

    .:EnoT:. Сексуальное чудовище

    Joined:
    29 May 2007
    Messages:
    803
    Likes Received:
    559
    Reputations:
    50
    Совершенно не понял про массив id шников.
    Но я как-то переделывал вышеупомянутую ф-цию примерно таким образом (в виде рекурсии).

    Возможно какие-то ошибки допустил, писал с ходу

    PHP:
    <?php

    function tree($id$parent_id=0){
            
        
    $sql mysql_query('SELECT * FROM `comments` WHERE `id` = '.intval($id).'
                            AND`parent_id` = '
    .$parent_id);
                            
        if(
    $sql && mysql_num_rows($sql)){
            
            while(
    $res mysql_fetch_object($sql)){
                
                if(
    $res->parent_id) return tree($id$res->parent_id);
                else return 
    $res->text;
            }
        }
    }           

    ?>
     
    #2 .:EnoT:., 7 Feb 2009
    Last edited: 20 Mar 2009
  3. AkyHa_MaTaTa

    AkyHa_MaTaTa Elder - Старейшина

    Joined:
    19 Mar 2007
    Messages:
    558
    Likes Received:
    306
    Reputations:
    27
    Имхо для таких ситуаций использования join(и не обязательно инер, для той структуры даных вполне можно и left) являеться более рациональным решением чем прибегать к нескольким запросам и тем более создавать какие то непонятные рекурсии на php которые при порядочном объеме данных и частых запросах могут сыграть злую шутку с сервером.l
     
    1 person likes this.
  4. Shadow_p1raT

    Shadow_p1raT Elder - Старейшина

    Joined:
    9 Mar 2008
    Messages:
    174
    Likes Received:
    93
    Reputations:
    0
    .:EnoT:. думаю вместо intval лучше заюзать is_numeric,так как цитирую
    взято от сюда
    _ttps:/threadnav56756-1-10.html
     
  5. Дикс

    Дикс Elder - Старейшина

    Joined:
    16 Apr 2006
    Messages:
    1,194
    Likes Received:
    227
    Reputations:
    26
    по поводу интвал - что за странная проверка?
    я всегда использовал if(intval($_GET['s']) > 0) $s = intval($_GET['s'])

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

    PHP:
    tree($id$parent_id=false){ 
         
        
    $parent_id $parent_id $parent_id NULL;
    это ваще че?))
    не проще так:
    PHP:
    tree($id$parent_id=NULL){
    AkyHa_MaTaTa
    ну а как, как его использовать?
     
    #5 Дикс, 7 Feb 2009
    Last edited: 7 Feb 2009
  6. AkyHa_MaTaTa

    AkyHa_MaTaTa Elder - Старейшина

    Joined:
    19 Mar 2007
    Messages:
    558
    Likes Received:
    306
    Reputations:
    27
    2 Shadow_p1raT не пойму при чем здеся is_numeric() так как у .:EnoT:. в примере идет intval($id) - которая всегда вернет int значения $id даже если оно будет отричательным тоже самое касаеться и is_numeric() оно вернет true даже при отрицатенльном значении $id, но, имхо, ты прав в другом что перед гверей лутче бы проверить данные на то что они являються число(если нам нужно число) и как сказал Дикс что оно больше чем 0, но как я понял .:EnoT:. привел в качестве примера(хотя при чем тут это я не пойму так как вопрос у Дикса немного другого характера).
    2 Дикс ну думаю гуглом ты умешь пользоваться не хуже чем я, там полно примеров с использование и описание JOIN в любых вариациях, вот неплохое обьяснения использования JOIN http://www.codinghorror.com/blog/archives/000976.html.
     
  7. Дикс

    Дикс Elder - Старейшина

    Joined:
    16 Apr 2006
    Messages:
    1,194
    Likes Received:
    227
    Reputations:
    26
    нужны два запроса:
    первый выдирает все нужные комменты с parent_id=null
    второй все, у которых в parent_id - айдишники уже выбранных.
    PHP:
    select from comments where parent_id=null limit 010
    select 
    from comments where parent_id in(список id из первого запроса)
    можно как-то объединить это в один запрос?
    типа вот так:
    PHP:
    select from trazh_comment as c1 inner join trazh_comment as c2 on c1.parentid=null or c2.parentid c1.cid limit 010
    выдаёт какую-то хрень с кучей одноименных столбцов.
    прежде чем копать дальше, хочу узнать - реально ли вообще объединить те два запроса в один?
     
  8. AkyHa_MaTaTa

    AkyHa_MaTaTa Elder - Старейшина

    Joined:
    19 Mar 2007
    Messages:
    558
    Likes Received:
    306
    Reputations:
    27
    В даном примере тебе не нужен JOIN так как у тебя 1 табличка, потому как я понял тебе надо что бы результат выборки одного запроса передовался в другой запрос для это легче и логичней использовать подзапрос, я не пойму что пытаешься зделать, у тебя 2 одинаковых запроса, то есть
    select * from comments where parent_id=null limit 0, 10

    вы берет все значения из comments parent_id =null а во втором запросе ты делаешь тоже самое

    select * from comments where parent_id in(список id из первого запроса)

    то есть фактически дублируешь 1 запрос, пытаешься
    найти все значения comments у которых id из первого запроса, то есть с parent_id = null(вернее 10 значений), не совсем понятна мне структура данных которые тебе необходимо выбрать.
     
  9. Pashkela

    Pashkela Динозавр

    Joined:
    10 Jan 2008
    Messages:
    2,750
    Likes Received:
    1,044
    Reputations:
    339
    PHP:
    select from comments where parent_id=null limit 010 
    select 
    from comments where parent_id in(список id из первого запроса
    а вот так конкретно это одним запросом:

    PHP:
    select from comments where parent_id=null limit 010 
    если мне логика не изменяет. Т.е. первый же запрос делает то, что тебе надо получить типо во втором
     
  10. Helios

    Helios Elder - Старейшина

    Joined:
    14 Jan 2007
    Messages:
    414
    Likes Received:
    180
    Reputations:
    103
    Как уже отмечали, средств для рекурсивных запросов в мускуле нет, но выбрать два уровня записей из дерева одним обращением к БД выбрать можно. Запрос будет выглядеть так:

    Code:
    SELECT *
    FROM `comments`
    WHERE `parent_id` = NULL
    OR `parent_id` IN (
        SELECT `id` FROM `comments` WHERE `parent_id` = NULL
    )
    
    Подзапросом мы вибираем идентификаторы тех элементов, которые не имеют родительских элементов, а основным запросом мы вибираем все те же корневые элементы и все их элементы-потомки.

    Проблему с несколькими обращениями к БД мы решили, теперь остается только придумать как обрабатывать полученные данные.
     
    1 person likes this.
  11. VDShark

    VDShark Elder - Старейшина

    Joined:
    1 Feb 2007
    Messages:
    260
    Likes Received:
    158
    Reputations:
    62
    Для 2-х уровней Хелиос написал адекватный запрос (в отличии от большинства написанного в этом топике). Если же необходимо большее количество уровней (зачастую неопределенное) впринципе можно использовать хп, но понадобится минимум 2 курсора. Преимуществ здесь будет 2:
    1) Сразу возвращаем полный и нужный нам результат
    2) Сохраняется план выполнения данных запросов на серваке, что, при правильном подходе,
    увеличит скорость работы.
     
  12. Дикс

    Дикс Elder - Старейшина

    Joined:
    16 Apr 2006
    Messages:
    1,194
    Likes Received:
    227
    Reputations:
    26
    Helios
    спасибо, как раз то, что нужно.
    я не знал что в in() можно передавать запрос.

    VDShark
    что такое хп и курсоры?
     
  13. AkyHa_MaTaTa

    AkyHa_MaTaTa Elder - Старейшина

    Joined:
    19 Mar 2007
    Messages:
    558
    Likes Received:
    306
    Reputations:
    27
    Я не вижу смысла выполнения этого запроса, критерии по которым выбераються данные при главном запросе идентичны тем которые идут в подзапросе, в итоге мы получаем тот же результат что и в основном запросе просто лишний раз проходим по таблице.
     
  14. VDShark

    VDShark Elder - Старейшина

    Joined:
    1 Feb 2007
    Messages:
    260
    Likes Received:
    158
    Reputations:
    62
    А если подумать немного? :) Выполни такие запросы и посмотри результат, м.б. натолкнет на ответ.

    > что такое хп и курсоры?
    ХП - сокращение от Хранимые Процедуры. Т.е. код хранящийся на стороне сервера и вызываемый по имени. А курсоры - это, грубо говоря, аналог циклов трационных языков программирования (другими словами - способ перемещения по кортежам и работы с ними, указатель на полученное отношение). Ну это я попытался рассказать попонятнее... Но можешь прочитать об этом в той же википедии (хоть там и не особо расписано) - http://ru.wikipedia.org/wiki/Курсор_(базы_данных)
     
    1 person likes this.
  15. AkyHa_MaTaTa

    AkyHa_MaTaTa Elder - Старейшина

    Joined:
    19 Mar 2007
    Messages:
    558
    Likes Received:
    306
    Reputations:
    27
    Может это тебе зделать(прежде чем что писать я всгда проверяю свои слова).
     
  16. Helios

    Helios Elder - Старейшина

    Joined:
    14 Jan 2007
    Messages:
    414
    Likes Received:
    180
    Reputations:
    103
    Запросы разные вообще-то и результаты выполнения их тоже разные, в чем можно убедиться, скормив запрос базе данных.

    Подзапрос возвращает ID элементов в корне. А основной запрос делает выборку этих элементов и их потомков. Без подзапроса ты просто этих потомков не найдешь.
     
  17. VDShark

    VDShark Elder - Старейшина

    Joined:
    1 Feb 2007
    Messages:
    260
    Likes Received:
    158
    Reputations:
    62
    Видимо данные на которых ты проверял были не учитывающими данную ситуацию... в чем я сильно сомневаюсь. Поэтому необходимо включать соображалку хотя бы иногда, а не безосновательно буровить на людей.
     
  18. Дикс

    Дикс Elder - Старейшина

    Joined:
    16 Apr 2006
    Messages:
    1,194
    Likes Received:
    227
    Reputations:
    26
    а всё-таки есть проблема с таким подходом.

    вот мой запрос:
    PHP:
     SELECT *
    FROM comment
    WHERE tid 
    =1
    AND parentid IS NULL
    OR parentid
    IN 
    (

    SELECT cid
    FROM comment
    WHERE tid 
    =1
    AND parentid IS NULL
    ORDER BY ctime ASC
    )
    ORDER BY ctime ASC
    LIMIT 0 
    10
    таким образом: сначала выполняется подзапрос. он возвращает ВСЕ id у которых tid=1 - ну типа комменты относятся к определенной теме.

    а потом уже мы выбираем вложенные комменты к этим комментам.

    но мне надо выбрать от n до m комментов в подзапросе, т.к. у меня пейджинг.
    а mysql говорит что её версия (5.0.15-nt) не поддерживает limit в подзапросах..

    как это исправить? есть ещё какие-то способы выполнить подзапрос, но с лимитом?
     
  19. AkyHa_MaTaTa

    AkyHa_MaTaTa Elder - Старейшина

    Joined:
    19 Mar 2007
    Messages:
    558
    Likes Received:
    306
    Reputations:
    27
    Ну вобшето я даже и не предполагал на кого то буровить, если это так воспринял то извеняй, а теперь по делу, если тебе несложно покажи ту структуру данных при которых даный запрос будет работать, ибо как ты заметил моей сображалки не хватает, покажи плиз свою.
     
  20. VDShark

    VDShark Elder - Старейшина

    Joined:
    1 Feb 2007
    Messages:
    260
    Likes Received:
    158
    Reputations:
    62
    Извиняю, надеюсь ты действительно не имел ввиду ничего дурного :)
    А по делу - если вообще не будет потомков (т.е. у всех записей будет pid=0), тогда вернутся идентичные рзультаты, иначе же будут утеряны родители (если запрос без where pid=0).