Laravel Fluent Query Builder 加入子查询

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18079281/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 02:35:06  来源:igfitidea点击:

Laravel Fluent Query Builder Join with subquery

laravellaravel-4

提问by driechel

Okay after hours of research and still using DB::select I have to ask this question. Because I am about to trough my computer away ;).

好的,经过数小时的研究并仍在使用 DB::select 我不得不问这个问题。因为我即将离开我的电脑;)。

I want to get the last input of a user (base on the timestamp). I can do this with the raw sql

我想获取用户的最后输入(基于时间戳)。我可以用原始 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

I got this query from another post hereon stackoverflow.

我从另一个帖子此查询这里的计算器。

I have tried everything to do this with the fluent query builder in Laravel however with no success.

我已经尝试使用 Laravel 中的流畅查询构建器来执行此操作,但没有成功。

I know the manual says you can do this:

我知道手册说你可以这样做:

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

But that is not helping much because I do not see how I could use a subquery there? Anyone who can light up my day?

但这并没有多大帮助,因为我不知道如何在那里使用子查询?谁能点亮我的一天?

回答by driechel

Ok for all of you out there that arrived here in desperation searching for the same problem. I hope you will find this quicker then I did ;O.

对于那些在绝望中寻找相同问题的人来说,好的。我希望你能比我更快地找到它;O。

This is how it is solved. JoostK told me at github that "the first argument to join is the table (or data) you're joining.". And he was right.

这就是它的解决方法。JoostK 在 github 告诉我“加入的第一个参数是你要加入的表(或数据)。”。他是对的。

Here is the code. Different table and names but you will get the idea right? It t

这是代码。不同的表和名称,但你会明白吗?它

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();

回答by ph4r05

I was looking for a solution to quite a related problem: finding the newest records per group which is a specialization of a typical greatest-n-per-groupwith N = 1.

我正在寻找一个相当相关问题的解决方案:找到每组的最新记录,这是N = 1的典型每组最大 n 的特化

The solution involves the problem you are dealing with here (i.e., how to build the query in Eloquent) so I am posting it as it might be helpful for others. It demonstrates a cleaner way of sub-query construction using powerful Eloquent fluent interface with multiple join columns and wherecondition inside joined sub-select.

该解决方案涉及您在此处处理的问题(即,如何在 Eloquent 中构建查询),因此我将其发布,因为它可能对其他人有帮助。它使用强大的 Eloquent fluent 接口演示了一种更简洁的子查询构建方式,其中包含多个连接列和where连接子选择内的条件。

In my example I want to fetch the newest DNS scan results (table scan_dns) per group identified by watch_id. I build the sub-query separately.

在我的示例中,我想获取scan_dnswatch_id. 我单独构建子查询。

The SQL I want Eloquent to generate:

我希望 Eloquent 生成的 SQL:

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`

I did it in the following way:

我是通过以下方式做到的:

// 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();

UPDATE:

更新:

Since Laravel 5.6.17the sub-query joinswere added so there is a native way to build the query.

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();

回答by shalonteoh

I think what you looking for is "joinSub". It's supported from laravel ^5.6. If you using laravel version below 5.6 you can also register it as macro in your app service provider file. like this https://github.com/teamtnt/laravel-scout-tntsearch-driver/issues/171#issuecomment-413062522

我想你要找的是“joinSub”。laravel ^5.6 支持它。如果您使用低于 5.6 的 Laravel 版本,您还可以在您的应用服务提供程序文件中将其注册为宏。像这样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.*']);

回答by Amit Meena

Query with sub query in Laravel

在 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();

回答by cby016

I can't comment because my reputation is not high enough. @Franklin Rivero if you are using Laravel 5.2 you can set the bindings on the main query instead of the join using the setBindings method.

我不能发表评论,因为我的声誉不够高。@Franklin Rivero 如果您使用的是 Laravel 5.2,则可以使用 setBindings 方法在主查询上设置绑定而不是连接。

So the main query in @ph4r05's answer would look something like this:

所以@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());

回答by Maksim Martianov

You can use following addon to handle all subquery related function from laravel 5.5+

您可以使用以下插件来处理 laravel 5.5+ 中所有与子查询相关的功能

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

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();