【问题标题】:Laravel Fluent Query Builder Join with subqueryLaravel Fluent 查询生成器加入子查询
【发布时间】:2013-08-07 09:45:41
【问题描述】:

好的,经过数小时的研究并仍在使用 DB::select 我必须问这个问题。因为我即将把我的电脑拿走;)。

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

我从另一个帖子 here 在 stackoverflow 上得到了这个查询。

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

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

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

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

【问题讨论】:

    标签: laravel laravel-4


    【解决方案1】:

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

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

    这里是代码。不同的表和名称,但你会明白吗?它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();
    

    【讨论】:

    • 这个包应该提供一个PHP工具集来构建查询,但是我们需要写下查询并通过selectRaw语句注入它!您是否有幸将流畅的查询对象而不是查询字符串传递给查询?
    • 如果您在子选择中有一个参数,这将变得不可能 - 例如,请参阅@ph4r05 答案,但我不确定是否可以简化它以避免那些丑陋的 toSql 和 addBinding
    • 谢谢!子查询的 Group By 部分很重要。我正在尝试一个类似的查询,我的计算值取决于每个连接的结果数量。我尝试了 Union ALL,但那是无法管理的。此解决方案完美运行。
    【解决方案2】:

    我正在寻找一个解决相当相关问题的方法:查找每个组的最新记录,这是 N = 1 的典型 greatest-n-per-group 的特化。

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

    在我的示例中,我想获取watch_id 标识的每个组的最新 DNS 扫描结果(表 scan_dns)。我单独构建子查询。

    我希望 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`
    

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

    // 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 sub-query joins,因此有一种构建查询的本地方式。

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

    【讨论】:

    • JoinCluse 对我来说没有 addBinding 方法,Laravel 5.2,还有其他建议吗?
    • 我发现新的 5.6 方式使用起来非常好用(使用 joinSub、leftJoinSub 等......)但我有一个问题,我的子查询没有考虑到 ON 条件给连接结果。我有类似$userInfoSubquery = DB::table('user_info') ->select() ->orderBy('info_date', 'DESC') ->limit(1); 的东西作为我的子查询。但是如果最新的信息不属于用户,它不会返回任何东西......所以我需要能够在子查询中以某种方式执行以下操作:->where('user_id', '=', userid)......
    【解决方案3】:

    我认为您正在寻找的是“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.*']);
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        我无法发表评论,因为我的声誉不够高。 @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());
        

        【讨论】:

          【解决方案6】:

          我在 Laravel 7.25 上,我不知道它是否支持以前的版本,但它非常好。

          函数语法:

          public function joinSub($query, $as, $first, $operator = null, $second = null, $type = 'inner', $where = false)

          例子:

          显示/获取用户 ID 和他们留下的帖子总数加入两个表用户和帖子。

                  return DB::table('users')
                      ->joinSub('select user_id,count(id) noOfPosts from posts group by user_id', 'totalPosts', 'users.id', '=', 'totalPosts.user_id', 'left')
                      ->select('users.name', 'totalPosts.noOfPosts')
                      ->get();
          

          替代方案:

          如果您不想为 leftjoin 提及“left”,那么您可以使用另一个预构建函数

              public function leftJoinSub($query, $as, $first, $operator = null, $second = null)
              {
                  return $this->joinSub($query, $as, $first, $operator, $second, 'left');
              }
          

          是的,它实际上调用了相同的函数,但它传递了连接类型本身。您可以对其他连接应用相同的逻辑,即 righJoinSub(...) 等。

          【讨论】:

            【解决方案7】:

            您可以使用以下插件来处理来自 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();
            

            【讨论】:

              猜你喜欢
              • 2021-12-03
              • 2021-02-20
              • 2015-12-13
              • 1970-01-01
              • 1970-01-01
              • 2020-03-12
              • 2021-04-27
              • 2017-06-04
              • 2015-07-10
              相关资源
              最近更新 更多