Як вибрати з підзапиту за допомогою 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;

Я шукаю кращого рішення.

Підкажіть, будь ласка, найпростіше рішення.

Відповіді:


131

На додаток до відповіді @ delmadord та ваших коментарів:

В даний час не існує методу створення підзапиту в FROMреченні, тому вам потрібно вручну використовувати оператор raw, тоді, якщо потрібно, ви об'єднаєте всі прив'язки:

$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()
Ярек Ткачик

5
Що стосується -> mergeBindings ($ sub-> getQuery ()) просто зробіть -> mergeBindings ($ sub)
Джиммі Іленлоа

1
@JimmyIlenloa Якщо $subзапит є Eloquent Builder , тоді вам все-таки потрібна ->getQuery()деталь, інакше ви отримаєте помилку, оскільки цей метод наведено на основі Query\Builderкласу.
Ярек Ткачик

1
@Kannan nope. я здогадуюсь, що це кандидат на PR, але врешті-решт це не дуже поширений випадок використання. Ймовірно, це причина того, що її немає до цього дня ..
Jarek Tkaczyk

77

Додано 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`

15

Рішення @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.
хагабака

1
Call to undefined method subSelect()здається subSelect, не існує.
Маруф Алом

3
Дякую, що повідомили про це, я неправильно написав це ім’я, воно мало бути selectSub. Зараз я оновив свою відповідь.
Саса Благоевич,

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 ('Ваш вибір')), щоб позбутися їх.
Вак,

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']для raw 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');

Також прив'язки будуть автоматично і правильно об'єднані з новим запитом.


Дуже дякую! Це дуже допомогло!
Хаснат Бабур
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.