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

问题描述 投票:0回答:12

我想通过使用 Eloquent ORM 的以下 SQL 来获取价值。

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

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

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

sql laravel laravel-4 eloquent query-builder
12个回答
170
投票

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

140
投票

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`

18
投票

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

特别注意如何在不使用

mergeBindings
方法的情况下制作
getQuery()


14
投票

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

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

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

12
投票

目前有许多可读的方法可以执行此类查询(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();

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

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

请注意,我使用的是

count()
,而不是显式编写
count(*)
语句并使用
get()
first()
来获取结果(您可以通过将
count()
替换为
selectRaw(count(*))->first()
轻松实现)。

原因很简单:它返回数字而不是具有尴尬命名属性的对象(

count(*)
除非您在查询中使用别名)

哪个更好看?

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

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

8
投票

此答案中描述的正确方式:https://stackoverflow.com/a/52772444/2519714 目前最流行的答案并不完全正确。

这种方式https://stackoverflow.com/a/24838367/2519714在某些情况下是不正确的,例如:子选择具有where绑定,然后将表连接到子选择,然后将其他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');

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


4
投票

我喜欢做这样的事情:

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

不是很优雅,但是很简单。


4
投票

这个效果很好

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

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

2
投票

我无法让您的代码执行所需的查询,AS 仅是表

abc
的别名,而不是派生表的别名。 Laravel 查询生成器不隐式支持派生表别名,为此很可能需要 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;

2
投票

源自 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

1
投票
->selectRaw('your subquery as somefield')

0
投票

$sub_query = DB::table('abc')->select(*)->groupby('col1');

$main_query = DB::table($sub_query,'S')->selectRaw("count(*)")

注意:- 'S' 是 $sub_query 的别名

© www.soinside.com 2019 - 2024. All rights reserved.