Как получить рейтинг с помощью вложенного запроса в Laravel?

#php #mysql #sql #laravel #window-functions

#php #mysql #sql #laravel #окно-функции

Вопрос:

Версия

Laravel: 7.28.3

mysql: версия 14.14 Дистрибутив 5.7.29, для osx10.15 (x86_64) с использованием оболочки EditLine


Таблицы

  1. содержимое (идентификатор)
  2. content_views (id, content_id)

Что я пытаюсь сделать

Я хотел бы получить рейтинг контента по количеству просмотров контента.


Код

App/Models/Content.php

   /**
   * Attribute of get rank by views
   *
   * @return Int
   */
  public function getViewsRankingAttribute()
  {
      DB::statement(DB::raw('set @c=0'));
      $result = collect(
          DB::select('select rank from
          (
              select _ranking.*, @c:=@c 1 as rank from
              (
                  select content_views.content_id, count(content_views.id) as views
                  from content_views
                  group by content_views.content_id
                  order by views desc
              ) as _ranking
          ) as ranking
          where content_id = :contentId', [
              'contentId' => $this->id
          ]))
          ->first();
      return $result ? $result->rank : '-';
  }
  

На самом деле это работает в mysql 5.8, но не в 8.0.


Ошибка

SQLSTATE[42000]: синтаксическая ошибка или нарушение доступа: 1064 У вас ошибка в синтаксисе SQL; проверьте руководство, соответствующее вашей версии сервера MySQL, чтобы найти правильный синтаксис для использования рядом с ‘from ( выберите _ranking., @c:=@c 1 как ранг из ‘ в строке 1 (SQL: выберите ранг из( выберите _ranking., @c:=@c 1 как ранг из ( выберите content_views.content_id, count(content_views.id ) как просмотры из группы content_views по content_views.content_id порядок просмотров desc ) как _ranking ) как ранжирование, где content_id = :ContentID)

Комментарии:

1. Появление CTE в 8 избавило от необходимости в таких переменных. Но, поскольку вы анализируете результат в PHP, почему бы не обработать ранжирование там?

2. @Strawberry: OP хочет получить рейтинг только определенного контента. Чтобы сделать это в PHP, им нужно будет получить весь набор данных (сгруппированный по содержимому), затем выполнить итерацию в приложении, что выглядит неоптимально.

Ответ №1:

В MySQL 8.0 представлены оконные функции, которые значительно упрощают выполнение задач ранжирования. С другой стороны, пользовательские переменные официально планируются к устареванию в будущих версиях.

Время принять будущее! Вы можете переписать свой запрос следующим образом:

 select *
from (
    select content_id, count(*) as views, rank() over(order by count(*) desc) rn
    from content_views
    group by content_id
) t
where content_id = :content_id
  

Ответ №2:

Это действительно сработало!

 public function getViewsRankingAttribute()
{
    $rank = '-';
    if ($this->views->isNotEmpty()) {
        $views = $this->views->count();
        $rank = Content::withCount('views')
            ->having('views_count', '>', $views)
            ->get()
            ->count();
        $rank  ;
    }
    return $rank;
}