如何使用 Laravel 查询生成器从子查询中进行选择?

我想通过以下 SQL 使用雄辩 ORM 获得价值。

- SQL

 SELECT COUNT(*) FROM
(SELECT * FROM abc GROUP BY col1) AS a;

然后我考虑了以下几点。

密码

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

我在寻找更好的解决办法。

请告诉我最简单的解决办法。

229694 次浏览

我不能让你的代码做所需的查询,AS 是一个别名只为表 abc,而不是为派生的表。 Laravel Query Builder 不隐式支持派生的表别名,因此很可能需要 DB: : raw。

我能想到的最直接的解决方案几乎与您的完全相同,但是会按您的要求生成查询:

$sql = Abc::groupBy('col1')->toSql();
$count = DB::table(DB::raw("($sql) AS a"))->count();

生成的查询是

select count(*) as aggregate from (select * from `abc` group by `col1`) AS a;

除了@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();

注意你需要用到 按正确顺序合并绑定。如果你有其他的约束从句,你必须把它们放在 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();

我喜欢这样做:

Message::select('*')
->from(DB::raw("( SELECT * FROM `messages`
WHERE `to_id` = ".Auth::id()." AND `isseen` = 0
GROUP BY `from_id` asc) as `sub`"))
->count();

不是很优雅,但很简单。

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

特别注意如何在不使用 getQuery()方法的情况下制作 mergeBindings

在 laravel 5.5中有一个专用于子查询的方法,您可以这样使用它:

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

或者

Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');

Laravel v5.6.12(2018-03-14)添加了 fromSub()fromRaw()方法来查询生成器 (# 23476)

公认的答案是正确的,但可以简化为:

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

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

select count(*) as aggregate from (select * from `abc` group by `col1`) as `a`

正确的回答方式如下: https://stackoverflow.com/a/52772444/2519714 目前最流行的答案并不完全正确。

这种方式在某些情况下 https://stackoverflow.com/a/24838367/2519714是不正确的,比如: sub select 有 where 绑定,然后将 table 连接到 sub select,然后将其他 where 添加到所有查询。例如查询: 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');

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

还有一个正确的方法:

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

此外,绑定将自动并正确地合并到新的查询。

目前有许多可读的方法来执行这些类型的查询(Laravel8)。

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

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

$count = DB::table(fn($sub) => $sub->from('abc')->groupBy('col1'), 'a')->count();

注意,我使用的是 count(),而不是显式地编写 count(*)语句,并使用 get()first()得到结果(通过用 selectRaw(count(*))->first()替换 count()可以很容易地实现这一点)。

原因很简单: 它返回的是数字,而不是一个具有名称奇怪的属性的对象(除非在查询中使用了别名,否则返回的是 count(*))

哪个看起来更好?

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


// using selectRaw('count(*)')->first() in the builder
echo $count->{'count(*)'};
->selectRaw('your subquery as somefield')

这样挺好的

$q1 = DB::table('tableA')->groupBy('col');


$data = DB::table(DB::raw("({$q1->toSql()}) as sub"))->mergeBindings($q1)->get();

Mpskovvang 的回答衍生出来的,下面是使用雄辩的模型看起来像什么。(我尝试更新 mpskovvang的答案,以包括这一点,但它有太多的编辑请求。)

$qry = Abc::where('col2', 'value')->groupBy('col1')->selectRaw('1');
$num = Abc::from($qry, 'q1')->count();
print $num;

生产..。

SELECT COUNT(*) as aggregate FROM (SELECT 1 FROM Abc WHERE col2='value' GROUP BY col1) as q1