Laravel Fluent Query Builder Присоединиться к подзапросу

56

Хорошо после нескольких часов исследований и до сих пор использующих DB:: select. Я должен задать этот вопрос. Потому что я собираюсь прогнать мой компьютер;).

Я хочу получить последний вход пользователя (база на отметке времени). Я могу сделать это с помощью raw sql

SELECT  c.*, p.*
FROM    users c INNER JOIN
(
  SELECT  user_id,
          MAX(created_at) MaxDate
  FROM    `catch-text`
  GROUP BY user_id
 ) MaxDates ON c.id = MaxDates.user_id INNER JOIN
    `catch-text` p ON   MaxDates.user_id = p.user_id
     AND MaxDates.MaxDate = p.created_at

Я получил этот запрос из другого сообщения здесь в stackoverflow.

Я пробовал все, чтобы сделать это с помощью свободного конструктора запросов в Laravel, но без успеха.

Я знаю, что в руководстве говорится, что вы можете это сделать:

DB::table('users')
    ->join('contacts', function($join)
    {
        $join->on('users.id', '=', 'contacts.user_id')->orOn(...);
    })
    ->get();

Но это не помогает, потому что я не вижу, как я могу использовать подзапрос там? Любой, кто может осветить мой день?

Теги:
laravel-4

6 ответов

119
Лучший ответ

Хорошо для всех вас, прибывших сюда в отчаянии, ищущих ту же проблему. Я надеюсь, вы найдете это быстрее, чем я; O.

Вот как это решается. JoostK сказал мне на github, что "первым аргументом для присоединения является таблица (или данные), к которой вы присоединяетесь". И он был прав.

Вот код Разные таблицы и названия, но вы поймете, правильно? Это т

DB::table('users')
        ->select('first_name', 'TotalCatches.*')

        ->join(DB::raw('(SELECT user_id, COUNT(user_id) TotalCatch,
               DATEDIFF(NOW(), MIN(created_at)) Days,
               COUNT(user_id)/DATEDIFF(NOW(), MIN(created_at))
               CatchesPerDay FROM 'catch-text' GROUP BY user_id)
               TotalCatches'), 
        function($join)
        {
           $join->on('users.id', '=', 'TotalCatches.user_id');
        })
        ->orderBy('TotalCatches.CatchesPerDay', 'DESC')
        ->get();
  • 4
    Предполагается, что этот пакет предоставляет набор инструментов PHP для создания запросов, но нам нужно записать запрос и внедрить его с selectRaw оператора selectRaw ! Вам посчастливилось передать в запрос свободный объект запроса вместо строки запроса?
  • 0
    Это становится невозможным, если у вас есть параметр в подвыборке - см., Например, ответ @ ph4r05, но я не уверен, что его можно упростить, чтобы избежать этих уродливых toSql и addBinding.
13

Я искал решение довольно связанной проблемы: поиск новейших записей на группу, который является специализацией типичного наибольшего n-на-группу с N = 1.

Решение включает в себя проблему, с которой вы здесь работаете (например, как построить запрос в Eloquent), поэтому я публикую его, так как он может быть полезен для других. Он демонстрирует более чистый способ создания подзапроса с использованием мощного интерфейса Eloquent, в котором имеется несколько столбцов соединения и where условие внутри присоединенного суб-выбора.

В моем примере я хочу получить новейшие результаты сканирования DNS (таблица scan_dns) для каждой группы, определенной watch_id. Я строю подзапрос отдельно.

SQL, который я хочу, чтобы Eloquent генерировал:

SELECT * FROM 'scan_dns' AS 's'
INNER JOIN (
  SELECT x.watch_id, MAX(x.last_scan_at) as last_scan
  FROM 'scan_dns' AS 'x'
  WHERE 'x'.'watch_id' IN (1,2,3,4,5,42)
  GROUP BY 'x'.'watch_id') AS ss
ON 's'.'watch_id' = 'ss'.'watch_id' AND 's'.'last_scan_at' = 'ss'.'last_scan'

Я сделал это следующим образом:

// table name of the model
$dnsTable = (new DnsResult())->getTable();

// groups to select in sub-query
$ids = collect([1,2,3,4,5,42]);

// sub-select to be joined on
$subq = DnsResult::query()
    ->select('x.watch_id')
    ->selectRaw('MAX(x.last_scan_at) as last_scan')
    ->from($dnsTable . ' AS x')
    ->whereIn('x.watch_id', $ids)
    ->groupBy('x.watch_id');
$qqSql = $subq->toSql();  // compiles to SQL

// the main query
$q = DnsResult::query()
    ->from($dnsTable . ' AS s')
    ->join(
        DB::raw('(' . $qqSql. ') AS ss'),
        function(JoinClause $join) use ($subq) {
            $join->on('s.watch_id', '=', 'ss.watch_id')
                 ->on('s.last_scan_at', '=', 'ss.last_scan')
                 ->addBinding($subq->getBindings());  
                 // bindings for sub-query WHERE added
        });

$results = $q->get();

ОБНОВИТЬ:

Начиная с Laravel 5.6.17 были добавлены объединения подзапросов, так что существует собственный способ создания запроса.

$latestPosts = DB::table('posts')
                   ->select('user_id', DB::raw('MAX(created_at) as last_post_created_at'))
                   ->where('is_published', true)
                   ->groupBy('user_id');

$users = DB::table('users')
        ->joinSub($latestPosts, 'latest_posts', function ($join) {
            $join->on('users.id', '=', 'latest_posts.user_id');
        })->get();
  • 0
    У JoinCluse нет метода addBinding для меня, Laravel 5.2, есть еще какие-нибудь предложения?
1

Я думаю, что вы ищете "joinSub". Поддерживается с laravel ^ 5.6. Если вы используете версию laravel ниже 5.6, вы также можете зарегистрировать ее как макрос в файле поставщика услуг приложения. как это https://github.com/teamtnt/laravel-scout-tntsearch-driver/issues/171#issuecomment-413062522

$subquery = DB::table('catch-text')
            ->select(DB::raw("user_id,MAX(created_at) as MaxDate"))
            ->groupBy('user_id');

$query = User::joinSub($subquery,'MaxDates',function($join){
          $join->on('users.id','=','MaxDates.user_id');
})->select(['users.*','MaxDates.*']);
1

Запрос с дополнительным запросом в Laravel

$resortData = DB::table('resort')
        ->leftJoin('country', 'resort.country', '=', 'country.id')
        ->leftJoin('states', 'resort.state', '=', 'states.id')
        ->leftJoin('city', 'resort.city', '=', 'city.id')
        ->select('resort.*', 'country.name as country_name', 'states.name as state_name','city.name as city_name', DB::raw("(SELECT GROUP_CONCAT(amenities.name) from resort_amenities LEFT JOIN amenities on amenities.id= resort_amenities.amenities_id WHERE resort_amenities.resort_id=resort.id) as amenities_name"))->groupBy('resort.id')
        ->orderBy('resort.id', 'DESC')
       ->get();
0

Вы можете использовать следующий аддон для обработки всех связанных с подзапросом функций из laravel 5. 5+

https://github.com/maksimru/eloquent-subquery-magic

User::selectRaw('user_id,comments_by_user.total_count')->leftJoinSubquery(
  //subquery
  Comment::selectRaw('user_id,count(*) total_count')
      ->groupBy('user_id'),
  //alias
  'comments_by_user', 
  //closure for "on" statement
  function ($join) {
      $join->on('users.id', '=', 'comments_by_user.user_id');
  }
)->get();
0

Я не могу комментировать, потому что моя репутация недостаточно высока. @Franklin Rivero, если вы используете Laravel 5.2, вы можете установить привязки для основного запроса вместо объединения, используя метод setBindings.

Таким образом, основной запрос в ответе @ph4r05 будет выглядеть примерно так:

$q = DnsResult::query()
    ->from($dnsTable . ' AS s')
    ->join(
        DB::raw('(' . $qqSql. ') AS ss'),
        function(JoinClause $join) {
            $join->on('s.watch_id', '=', 'ss.watch_id')
                 ->on('s.last_scan_at', '=', 'ss.last_scan');
        })
    ->setBindings($subq->getBindings());

Ещё вопросы

Сообщество Overcoder
Наверх
Меню