本文搬運自我自己的部落格
目前有這樣兩個需求:
1) 查詢使用者,顯示使用者的資訊以及他寫過的書籍。如果使用者有書籍,則顯示,最多顯示2本。如果沒有,則不顯示書籍。
2) 顯示使用者的id號以及對應的書籍件數(只用SQL實現,不使用業務邏輯)。
本文中用到的user
模型,資料,控制器,路由之類的都已經在另一篇文章 手摸手教你讓Laravel開發Api更得心應手 建立好了。
users
表中的資料
books
表中的資料
這個比較容易,只要在關聯函式限制條數即可。
3.1. 建立Book模型
php artisan make:model Models/Book
3.2. 新增關聯函式並且限制條數
編輯 app/Models/User.php
,新增關聯函式
public function books(){
return $this->hasMany(Book::class,'user_id','id')->limit(2); //一對多,最多關聯2條
}
3.3. 測試
在app/Http/Controllers/Api/UserController.php
裡,隨意新增一個測試函式
//關聯查詢限制條數
public function test(Request $request){
$users= User::with('books')->get();
return $users;
}
測試結果,符合要求,id為1的使用者原來是3本書籍,現在只被取出2本。
4.1. SQL語句
一開始,我們會這樣寫SQL
語句
select `u`.`id`,`u`.`name`,`num` from `users` as `u` left join (select `user_id`,count(*) as `num` from books group by `user_id`) as `b` on `u`.id = `b`.user_id
最後顯示如下,並不會將沒有的顯示為0
所以我們稍加修改,用上MySQL
的內建函式
select distinct `u`.`id`,`u`.`name`,IFNULL( `b`.`num`, 0 ) AS num from `users` as `u` left join (select `user_id`,count(*) as `num` from books group by `user_id`) as `b` on `u`.id = `b`.user_id
符合我們的需求。
4.2. Laravel框架中使用
寫SQL很容易,那我們應該如何在框架中使用呢(不允許查完再用業務邏輯後獲得答案)?同時我們再附加一個條件,只要id為1
,2
,3
,4
,5
的使用者。
4.2.1. 直接編寫
查詢Laravel手冊,參考查詢構造器
的高階join語句
,我們會立刻想到下面這樣編寫
public function test3(){
//統計出所有內部員工的user_id
$user_ids = [1,2,3,4,5];
$users = User::selectRaw('u.id,IFNULL( b.number, 0 ) AS number')
->from('users as u')
->distinct()
->whereIn('id', $user_ids)
->leftJoin('books as b',function ($join) use($user_ids){
$join->selectRaw('user_id,count(*) as number')->whereIn('user_id', $user_ids)->groupBy('user_id')->on('u.id', '=', 'b.user_id');
})
->get();
return $users;
}
測試的時候我們發現報了錯
Unknown column 'b.number' in 'field list' (SQL: select distinct u.id,IFNULL( b.number, 0 ) AS number from `users` as `u` left join `books` as `b` on `user_id` in (1, 2, 3, 4, 5) and `u`.`id` = `b`.`user_id` where `id` in (1, 2, 3, 4, 5))
最後的SQL語句跟我們想象中的不太一樣。
4.2.2. 問題分析
錯誤的原因是,我們其實是使用left join
連線了子查詢,但是Laravel
的聯表查詢,例如join
,lefeJoin
,rightJoin
等,經過個人的測試,這些閉包並不能實現子查詢的。所以最後獲得的SQL語句是錯誤的。
Laravel官方文件的子查詢並沒有這方面詳細的介紹,所以我們一起來了解一下其他地方查來的資料
4.2.3. Query Builder
4.2.3.1. toSql()
toSql()
方法的作用是為了獲取不帶有binding
引數的SQL
例如:
select * from `users` where `users`.`id` = ?
4.2.3.2. getQuery()
getQuery()
方法的作用是為了獲取binding
引數並代替toSql()
獲得SQL
的問號,從而得到完整的SQL
例如:
select * from `users` where `users`.`id` = 1
4.2.4. 修復問題
現在我們使用Query Builder
來修復一下之前的問題
public function test2(){
//統計出所有內部員工的user_id
$user_ids = [1,2,3,4,5];
$bookQuery = Book::selectRaw('user_id,count(*) as number')->whereIn('user_id', $user_ids)->groupBy('user_id'); //製作一個query builder
$users = User::selectRaw('u.id,IFNULL( b.number, 0 ) AS number')
->from('users as u')
->distinct()
->whereIn('id', $user_ids)
->leftJoin(\DB::raw("({$bookQuery->toSql()}) as b"),function ($join) use($bookQuery){
//toSql()返回的是等待繫結引數的SQL語句
$join->mergeBindings($bookQuery->getQuery())->on('u.id','=','b.user_id');
//mergeBindings是將SQl的引數進行繫結
})
->get();
return $users;
}
4.2.5. 測試
最後的結果符合我們的需求
本作品採用《CC 協議》,轉載必須註明作者和本文連結