จะเลือกจากแบบสอบถามย่อยโดยใช้ Laravel Query Builder ได้อย่างไร?


104

ฉันต้องการรับค่าด้วย 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;

ฉันกำลังมองหาทางออกที่ดีกว่า

โปรดบอกวิธีแก้ปัญหาที่ง่ายที่สุด

คำตอบ:


136

นอกเหนือจากคำตอบของ @ 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)
Jimmy Ilenloa

1
@JimmyIlenloa หาก$subเคียวรีเป็นEloquent Builder แสดงว่าคุณยังต้องการ->getQuery()ส่วนนั้นอยู่มิฉะนั้นคุณจะได้รับข้อผิดพลาดเนื่องจากเมธอดนี้ถูกพิมพ์เทียบกับQuery\Builderคลาส
Jarek Tkaczyk

1
@ คุณกานต์ nope. ฉันเดาว่ามันเป็นผู้สมัคร PR แต่ในที่สุดก็ไม่ใช่กรณีการใช้งานทั่วไป คงเป็นเหตุผลที่ไม่มีมาจนถึงทุกวันนี้ ..
Jarek Tkaczyk

82

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`

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()งานได้ผลสำหรับฉัน
Nino Škopac

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ไม่มีอยู่จริง
Maruf Alom

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 เพิ่มเครื่องหมายคำพูดและฉันต้องใช้ -> เลือก (\ DB :: raw ('Your select')) เพื่อกำจัดสิ่งเหล่านี้
กล่อมเกลา

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 :: โดยที่ ('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');

นอกจากนี้การเชื่อมโยงจะถูกรวมเข้ากับแบบสอบถามใหม่โดยอัตโนมัติและถูกต้อง


ขอบคุณมาก! มันช่วยได้มาก!
Hasnat Babur
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.