Есть ли решение для пагинации древовидных комментариев?

Друзья. Подскажите, есть ли какое-либо решение для пагинации древовидных комментариев?
Необходимо сделать систему общения, похожую на эту: shikimori.org (1 родительский комментарий, остальные со вложенностью 2 уровня) с ПАГИНАЦИЕЙ! Т.е. должна быть кнопка "отобразить еще комментарии" как у первого уровня, так и у второго уровня.
Из всего прочитанного в интернете нашел два варианта решений:
Первый вариант:
www.9lessons.info/2011/05/facebook-wall-script-wit...
Сделать две таблицы в БД:
1 таблица - все комментарии первого уровня.
2 таблица - все комментарии второго уровня с ключом ID от первого уровня, по которому они подгружаются к комментарию первого уровня.
т.е.
select комменты первого уровня
while() {
if (есть ответы на коммент) {
select комменты второго уровня where parent = id коммента первого уровня
}
}

Но данная реализация очень ресурсозатратна, верно? Делать Select в цикле while, как я понимаю, не есть хорошо?
Планируется 10 комментариев первого уровня на страницу.. Если у этих 10 комментариев будут дочерние комментарии, то запросов будет 11.
Плюсы данной реализации:
В первом запросе, когда SELECT всех комментов первого уровня, можно задать четкий LIMIT. Также можно задать четкий LIMIT у дочерних комментариев. Эти LIMITы нужны для пагинации (возможности подгружать больше комментариев, как тут: easycaptures.com/fs/uploaded/1045/7634907040.jpg)
Минусы: Высокая нагрузка на БД ?!
-----------------------------------------------
Второй вариант:
Одна таблица со всеми комментариями.
Делаем всего 1 запрос к БД - SELECT всех комментариев к новости вместе с дочерними с сортировкой по дате.
У нас получается массив со всеми комментариями к новости.
Обрабатываем данный массив функцией рекурсии:
function dotree(&$comments, $parentComment = 0, $level = 0, $count = null){    
  if (is_array($comments) && count($comments)){         
    $return = array();
    if (is_null($count)){
      $c = count($comments);
    }else{
      $c = $count;
    }
    for($i=0;$i<$c;$i++){       
      if (!isset($comments[$i])) { continue; }
      $comment = $comments[$i];
      $parentId = $comment['parent'];
      if ($parentId == $parentComment){
          
        $comment['level'] = $level;
        $commentId = $comment['id'];
        $return[] = $comment;
        unset($comments[$i]);
        while ($nextReturn = dotree($comments, $commentId, $level+1, $c)){
          $nextReturn = array_reverse($nextReturn);
          $return = array_merge($return, $nextReturn);
        }
      } 
    }    
    return $return;
  }
  return false;
}


Получаем массив со всеми комментариями к новости, вида:
1
1.1
1.2
2
3
3.1
3.2
3.3
Как вывести первые 3 родительских комментариев с дочерними подкомментариями? Если поставить LIMIT 3 в этот единственный запрос, то выберется три первых записи, отсортированные по дате. Не факт, что там будут 3 родительских.

Так как разумно сделать возможность пагинации (навигации) по комментариям, чтобы можно было подгружать большее кол-во комментариев с доп. запросов через json?

Пару дней ищу разумное решение, пожалуйста, подскажите кто-нибудь.
  • Вопрос задан
  • 708 просмотров
Пригласить эксперта
Ответы на вопрос 3
Melkij
@Melkij
PostgreSQL DBA
Начните с изучения вопроса, как можно хранить деревья в РСУБД.
Для ограниченного количества вложенности - материализованной путь отлично подходит. И элементарно выводится limit'ом, можно даже без offset'а обойтись, если озадачиться.
Ответ написан
Комментировать
saDam
@saDam
Microservices, .NET Core, EF Core, SQL, RabbitMQ,
Во первых ну нужны две таблицы это бред, все что вам нужно это поле ParentId, это поле внешнего ключа на Id комментария выше стоящего уровнем.
У вас в базе получается примерно такие строки:
Id: 1 ParentId: null,
Id:2 ParentId: 1,
Id:3 ParentId: 1,
Id:4 ParentId: null,
Id:5 ParentId: 4

Что видно из данной структуры:
1. Что все сообщения 1 -го уровня имеют ParentId == null.
2. В каждом сообщении нужно ввести поле CreateDate, в которое будет писаться время создания сообщения(НЕ ПУТАТЬ С ВРЕМЕНЕМ РЕДАТИРОВАНИЯ, если такое возможно). Это поле вам пригодится чтобы сортировать самые свежие сообщение (ORDER BY DESC и TOP 3 вам в помощь).
3. При первой прогрузке страницы вам нужен запрос в бд ТОП 10 где ParentId == null, и для каждой такой взять TOP 3 предварительно отсортированных ORDER BY DESC CreateDate.

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

Что нужно не забыть в бекэнде такого метода:
Пропуск уже отображенных комментариев при первой прогрузке, если идет речь о комментариях первого уровня(ParentId == null), то вам нужен Skip(10), если второго уровня то Skip(3).

На что стоит обратить внимание:
Не забывайте что у вас все время добавляются комментарии, если вы на момент прогрузки брали 3 самых свежих коммента, то нужно учитывать, что к моменту нажатия прогрузки еще 10 новых комментариев, уже могли накомментировать новых три и тогда вы возможно опять прогрузите эти же три сообщения в какой то момент. Как проблему решать? Тут зависит от того как у вас должно все работать, вы можете в метод передавать дополнительную Id последнее сообщения и брать TOP(10) где дата < комментария найденного по Id. Либо если вам нужно прогружать и новые, то тут уже посерьезней нужно что то думать, либо кнопкой подгрузки новых сообщения, либо проверить при прогрузке есть ли новые итп... Тут нужно ваше решение.
Ответ написан
hummingbird
@hummingbird
Я предлагаю реализовать все на ajax. Тупо жмешь на ссылки, передаешь параметры, а далее, в зависимости от параметров, выводишь в нужный div c ID нужный список комментариев.

Притом, по логике, для вывода любого уровня списка, используется одна и та же функция как на ajax, так и на php. Меняются только передаваемы параметры (например, id блока).
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы