如何使用Laravel Query Builder从子查询中进行选择?

que*_*658 82 sql query-builder laravel eloquent laravel-4

我想使用Eloquent ORM通过以下SQL获得价值.

- SQL

 SELECT COUNT(*) FROM 
 (SELECT * FROM abc GROUP BY col1) AS a;
Run Code Online (Sandbox Code Playgroud)

然后我考虑了以下内容.

- 代码

 $sql = Abc::from('abc AS a')->groupBy('col1')->toSql();
 $num = Abc::from(\DB::raw($sql))->count();
 print $num;
Run Code Online (Sandbox Code Playgroud)

我正在寻找更好的解决方案.

请告诉我最简单的解决方案.

Jar*_*zyk 114

除了@ delmadord的回答和你的意见:

目前没有方法可以在FROM子句中创建子查询,因此您需要手动使用原始语句,然后,如有必要,您将合并所有绑定:

$sub = Abc::where(..)->groupBy(..); // Eloquent Builder instance

$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )
    ->mergeBindings($sub->getQuery()) // you need to get underlying Query Builder
    ->count();
Run Code Online (Sandbox Code Playgroud)

请注意,您需要以正确的顺序合并绑定.如果您有其他绑定条款,则必须在mergeBindings以下情况下将它们放在:

$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )

    // ->where(..) wrong

    ->mergeBindings($sub->getQuery()) // you need to get underlying Query Builder

    // ->where(..) correct

    ->count();
Run Code Online (Sandbox Code Playgroud)

  • 关于*** - > mergeBindings($ sub-> getQuery())***只做*** - > mergeBindings($ sub)*** (4认同)
  • 注意,如果你有一个复杂的查询作为`belongsToMany`作为subselect你必须添加`getQuery()`两次=>`$ sub-> getQuery() - > getQuery()` (3认同)
  • @JimmyIlenloa 如果 `$sub` 查询是一个 *Eloquent Builder*,那么您仍然需要 `->getQuery()` 部分,否则您会收到错误,因为此方法是针对 `Query\Builder` 类进行类型提示的。 (2认同)

mps*_*ang 52

Laravel v5.6.12(2018年3月14日)加fromSub()fromRaw()方法来查询生成器(#23476) .

接受的答案是正确的,但可以简化为:

DB::query()->fromSub(function ($query) {
    $query->from('abc')->groupBy('col1');
}, 'a')->count();
Run Code Online (Sandbox Code Playgroud)

上面的代码片段产生以下SQL:

select count(*) as aggregate from (select * from `abc` group by `col1`) as `a`
Run Code Online (Sandbox Code Playgroud)


IGP*_*IGP 14

目前有许多可读的方法可以执行此类查询(Laravel 8)。

// option 1: DB::table(Closure, alias) for subquery
$count = DB::table(function ($sub) {
        $sub->from('abc')
            ->groupBy('col1');
    }, 'a')
    ->count();

// option 2: DB::table(Builder, alias) for subquery
$sub   = DB::table('abc')->groupBy('col1');
$count = DB::table($sub, 'a')->count();

// option 3: DB::query()->from(Closure, alias)
$count = DB::query()
    ->from(function ($sub) {
        $sub->from('abc')
            ->groupBy('col1')
    }, 'a')
    ->count();

// option 4: DB::query()->from(Builder, alias)
$sub   = DB::table('abc')->groupBy('col1');
$count = DB::query()->from($sub, 'a')->count();
Run Code Online (Sandbox Code Playgroud)

对于如此小的子查询,您甚至可以尝试使用 PHP 7.4 的短闭包将它们放在一行中,但这种方法可能更难以维护。

// option 1: DB::table(Closure, alias) for subquery
$count = DB::table(function ($sub) {
        $sub->from('abc')
            ->groupBy('col1');
    }, 'a')
    ->count();

// option 2: DB::table(Builder, alias) for subquery
$sub   = DB::table('abc')->groupBy('col1');
$count = DB::table($sub, 'a')->count();

// option 3: DB::query()->from(Closure, alias)
$count = DB::query()
    ->from(function ($sub) {
        $sub->from('abc')
            ->groupBy('col1')
    }, 'a')
    ->count();

// option 4: DB::query()->from(Builder, alias)
$sub   = DB::table('abc')->groupBy('col1');
$count = DB::query()->from($sub, 'a')->count();
Run Code Online (Sandbox Code Playgroud)

请注意,我使用的是count()而不是显式编写count(*)语句并使用get()orfirst()来获取结果(您可以通过替换为 轻松完成count()selectRaw(count(*))->first()

原因很简单:它返回数字而不是具有笨拙命名属性的对象(count(*)除非您在查询中使用别名)

哪个看起来更好?

// using count() in the builder
echo $count;

// using selectRaw('count(*)')->first() in the builder
echo $count->{'count(*)'};
Run Code Online (Sandbox Code Playgroud)


Thi*_*ata 11

@JarekTkaczyk的解决方案正是我所寻找的.我唯一想念的是当你使用DB::table()查询时如何做到这一点 .在这种情况下,我就是这样做的:

$other = DB::table( DB::raw("({$sub->toSql()}) as sub") )->select(
    'something', 
    DB::raw('sum( qty ) as qty'), 
    'foo', 
    'bar'
);
$other->mergeBindings( $sub );
$other->groupBy('something');
$other->groupBy('foo');
$other->groupBy('bar');
print $other->toSql();
$other->get();
Run Code Online (Sandbox Code Playgroud)

特别注意如何mergeBindings不使用该getQuery()方法


Sas*_*vic 10

从 laravel 5.5 开始,有一个专门的子查询方法,你可以像这样使用它:

Abc::selectSub(function($q) {
    $q->select('*')->groupBy('col1');
}, 'a')->count('a.*');
Run Code Online (Sandbox Code Playgroud)

或者

Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');
Run Code Online (Sandbox Code Playgroud)

  • 感谢您让我注意到这一点,我拼错了名字,它应该是`selectSub`。我现在更新了我的回复。 (3认同)
  • 看来subSelect只能用于向SELECT添加子查询,而不能用于FROM。 (2认同)

dko*_*kop 5

此答案中描述的正确方法:https : //stackoverflow.com/a/52772444/2519714 当前最受欢迎的答案并不完全正确。

这样/sf/answers/1738685721/在某些情况下是不正确的,例如:子选择具有 where 绑定,然后将表连接到子选择,然后将其他 wheres 添加到所有查询中。例如查询: select * from (select * from t1 where col1 = ?) join t2 on col1 = col2 and col3 = ? where t2.col4 = ? 要进行此查询,您将编写如下代码:

$subQuery = DB::query()->from('t1')->where('t1.col1', 'val1');
$query = DB::query()->from(DB::raw('('. $subQuery->toSql() . ') AS subquery'))
    ->mergeBindings($subQuery->getBindings());
$query->join('t2', function(JoinClause $join) {
    $join->on('subquery.col1', 't2.col2');
    $join->where('t2.col3', 'val3');
})->where('t2.col4', 'val4');
Run Code Online (Sandbox Code Playgroud)

在执行此查询期间,他的方法$query->getBindings()将以不正确的顺序返回绑定,例如['val3', 'val1', 'val4']在这种情况下,而不是['val1', 'val3', 'val4']对上述原始 sql正确。

另一种正确的方法来做到这一点:

$subQuery = DB::query()->from('t1')->where('t1.col1', 'val1');
$query = DB::query()->fromSub($subQuery, 'subquery');
$query->join('t2', function(JoinClause $join) {
    $join->on('subquery.col1', 't2.col2');
    $join->where('t2.col3', 'val3');
})->where('t2.col4', 'val4');
Run Code Online (Sandbox Code Playgroud)

绑定也会自动正确地合并到新查询中。