Как выбрать из подзапроса с помощью Laravel Query Builder?

102

Я хотел бы получить значение с помощью следующего SQL с использованием Eloquent 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;

Я ищу лучшее решение.

Подскажите пожалуйста самое простое решение.

quenty658
источник

Ответы:

134

В дополнение к ответу @ 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();
Ярек Ткачик
источник
3
Обратите внимание, что если у вас есть сложный запрос в качестве подзапроса, belongsToManyвам нужно добавить getQuery()дважды =>$sub->getQuery()->getQuery()
Jordi Puigdellívol
1
@Skyzer Ты не читаешь то, что я пишу. Когда ты звонишь, ничего не ускользает toSql. Прочтите о PDO php.net/manual/en/book.pdo.php и посмотрите результат вашего$query->toSql()
Jarek Tkaczyk
5
Что касается -> mergeBindings ($ sub-> getQuery ()), просто сделайте -> mergeBindings ($ sub)
Джимми Иленлоа
1
@JimmyIlenloa Если $subзапрос является Eloquent Builder , то вам все равно нужна ->getQuery()часть, иначе вы получите ошибку, поскольку этот метод намекает на Query\Builderкласс.
Ярек Ткачик,
1
@Kannan нет. Я думаю, это кандидат на звание PR, но, в конце концов, это не очень распространенный вариант использования. Вероятно, это причина того, что его там нет до сих пор ..
Ярек Ткачик
80

Добавлено 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`
mpskovvang
источник
16

Решение @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()метода

Тьяго Мата
источник
Использование DB::raw()
сработало
7

В laravel 5.5 есть специальный метод для подзапросов, и вы можете использовать его следующим образом:

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

или

Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');
Саша Благоевич
источник
1
Кажется, что subSelect может использоваться только для добавления подзапроса в SELECT, а не FROM.
hagabaka
1
Call to undefined method subSelect()похоже subSelect, не существует.
Маруф Алом
3
Спасибо, что обратил на это мое внимание, я неправильно написал имя, так и должно было быть selectSub. Я обновил свой ответ.
Sasa Blagojevic
3

Мне нравится делать что-то вроде этого:

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

Это не очень изящно, но все просто.

Гай Мазуз
источник
Спасибо, это сработало для меня, в качестве примечания, будьте осторожны с выбранным контентом, потому что laravel добавил несколько кавычек, и мне пришлось использовать -> select (\ DB :: raw ('Your select')), чтобы избавиться от них.
Wak
2

Я не смог заставить ваш код выполнять желаемый запрос, 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;
Питер Бабич
источник
Спасибо за ваш ответ. Проблема в методе "Abc :: from (???) и DB :: table (???)". $ sql = Abc :: where ('id', '=', $ id) -> groupBy ('col1') -> toSql (); $ count = DB :: table (DB :: raw ("($ sql) AS a")) -> count (); Ошибка SQL возникает в приведенном выше коде. - где и параметр назначить!
quenty658
2

Правильный способ, описанный в этом ответе: https://stackoverflow.com/a/52772444/2519714 Самый популярный ответ на данный момент не совсем правильный.

Таким образом, https://stackoverflow.com/a/24838367/2519714 неверен в некоторых случаях, например: sub select имеет где привязки, затем присоединение таблицы к дополнительному выбору, затем другие точки, добавленные ко всем запросам. Например, запрос: 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']в этом случае, вместо правильного ['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');

Также привязки будут автоматически и правильно объединены с новым запросом.

dkop
источник
Большое спасибо! Это очень помогло!
Хаснат Бабур