Bagaimana cara memilih dari subquery menggunakan Laravel Query Builder?

102

Saya ingin mendapatkan nilai dengan SQL berikut menggunakan Eloquent ORM.

- SQL

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

Kemudian saya mempertimbangkan yang berikut ini.

- Kode

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

Saya mencari solusi yang lebih baik.

Tolong beri tahu saya solusi paling sederhana.

quenty658
sumber

Jawaban:

131

Selain jawaban @ delmadord dan komentar Anda:

Saat ini tidak ada metode untuk membuat subquery dalam FROMklausa, jadi Anda perlu menggunakan pernyataan mentah secara manual, kemudian, jika perlu, Anda akan menggabungkan semua binding:

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

Ingatlah bahwa Anda perlu menggabungkan binding dalam urutan yang benar . Jika Anda memiliki klausa terikat lainnya, Anda harus meletakkannya setelah 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();
Jarek Tkaczyk
sumber
3
Perhatikan bahwa jika Anda memiliki kueri kompleks belongsToManysebagai subpilihan Anda harus menambahkan getQuery()dua kali =>$sub->getQuery()->getQuery()
Jordi Puigdellívol
1
@ Skyzer Anda tidak membaca apa yang saya tulis. Tidak ada yang lolos saat Anda menelepon toSql. Baca tentang PDO php.net/manual/en/book.pdo.php dan lihat hasil dari$query->toSql()
Jarek Tkaczyk
5
Berkenaan dengan -> mergeBindings ($ sub-> getQuery ()) lakukan saja -> mergeBindings ($ sub)
Jimmy Ilenloa
1
@JimmyIlenloa Jika $subkueri adalah Eloquent Builder , maka Anda masih memerlukan ->getQuery()bagian tersebut, jika tidak, Anda akan mendapatkan kesalahan, karena metode ini diketikkan terhadap Query\Builderkelas.
Jarek Tkaczyk
1
@Tokopedia Saya kira itu adalah calon PR, tapi pada akhirnya itu bukan kasus penggunaan yang umum. Mungkin inilah alasan mengapa tidak memilikinya sampai hari ini ..
Jarek Tkaczyk
76

Laravel v5.6.12 (2018-03-14) ditambahkan fromSub()dan fromRaw()metode untuk membuat kueri (# 23476) .

Jawaban yang diterima benar tetapi dapat disederhanakan menjadi:

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

Cuplikan di atas menghasilkan SQL berikut:

select count(*) as aggregate from (select * from `abc` group by `col1`) as `a`
mpskovvang.dll
sumber
15

Solusi @JarekTkaczyk persis seperti yang saya cari. Satu-satunya hal yang saya lewatkan adalah bagaimana melakukannya saat Anda menggunakan DB::table()kueri. Dalam hal ini, inilah cara saya melakukannya:

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

Perhatian khusus bagaimana membuat mergeBindingstanpa menggunakan getQuery()metode

Thiago Mata
sumber
Menggunakan DB::raw()melakukan pekerjaan itu untuk saya
Nino Škopac
7

Dari laravel 5.5 ada metode khusus untuk subkueri dan Anda dapat menggunakannya seperti ini:

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

atau

Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');
Sasa Blagojevic
sumber
1
Tampaknya subSelect hanya dapat digunakan untuk menambahkan sub-query ke SELECT, bukan FROM.
hagabaka
1
Call to undefined method subSelect()sepertinya subSelecttidak ada.
Maruf Alom
3
Terima kasih telah memberitahukan hal ini kepada saya, saya salah mengeja namanya, seharusnya begitu selectSub. Saya telah memperbarui tanggapan saya sekarang.
Sasa Blagojevic
3

Saya suka melakukan sesuatu seperti ini:

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

Ini tidak terlalu elegan, tapi sederhana.

Guy Mazuz
sumber
Terima kasih ini berhasil untuk saya, sebagai catatan tambahan, hati-hati dengan konten yang dipilih karena laravel menambahkan beberapa tanda kutip dan saya harus menggunakan -> pilih (\ DB :: raw ('Pilihan Anda')) untuk menyingkirkannya.
Wakak
2

Saya tidak dapat membuat kode Anda untuk melakukan kueri yang diinginkan, AS adalah alias hanya untuk tabel abc, bukan untuk tabel turunan. Laravel Query Builder tidak secara implisit mendukung alias tabel turunan, DB :: raw kemungkinan besar diperlukan untuk ini.

Solusi paling langsung yang dapat saya berikan hampir sama dengan solusi Anda, namun menghasilkan kueri seperti yang Anda minta:

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

Kueri yang dihasilkan adalah

select count(*) as aggregate from (select * from `abc` group by `col1`) AS a;
peter.babic
sumber
Terimakasih atas balasan anda. Ada masalah dalam metode "Abc :: from (???) dan DB :: table (???)". $ sql = Abc :: where ('id', '=', $ id) -> groupBy ('col1') -> toSql (); $ count = DB :: table (DB :: raw ("($ sql) AS a")) -> count (); Kesalahan SQL terjadi pada kode di atas. - dimana dan parameter ditetapkan!
quenty658
2

Cara yang benar dijelaskan dalam jawaban ini: https://stackoverflow.com/a/52772444/2519714 Jawaban paling populer saat ini tidak sepenuhnya benar.

Cara ini https://stackoverflow.com/a/24838367/2519714 tidak benar dalam beberapa kasus seperti: sub pilih memiliki binding, lalu bergabung dengan tabel ke sub pilih, lalu yang lain ditambahkan ke semua kueri. Contoh query: select * from (select * from t1 where col1 = ?) join t2 on col1 = col2 and col3 = ? where t2.col4 = ? Untuk membuat query ini Anda akan menulis kode seperti:

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

Selama menjalankan kueri ini, metodenya $query->getBindings()akan mengembalikan binding dalam urutan yang salah seperti ['val3', 'val1', 'val4']dalam kasus ini, bukan mengoreksi ['val1', 'val3', 'val4']sql mentah yang dijelaskan di atas.

Sekali lagi cara yang benar untuk melakukan ini:

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

Binding juga akan secara otomatis dan benar digabungkan ke kueri baru.

dkop
sumber
Terima kasih banyak! Itu sangat membantu!
Hasnat Babur