資料庫:查詢建立器
- 簡介
- 執行資料庫查詢
- Select 語句
- 原始表達式
- Joins(聯結)
- Unions(聯集)
- 基本 Where 條件子句
- 進階 Where 條件子句
- 排序、分組、限制和偏移
- 條件子句
- Insert 語句
- Update 語句
- Delete 語句
- 悲觀鎖定
- 除錯
簡介
Laravel 的資料庫查詢建立器提供了一個方便、流暢的介面來建立和執行資料庫查詢。它可以用於在您的應用程式中執行大多數資料庫操作,並且可以與 Laravel 支援的所有資料庫系統完美配合。
Laravel 查詢建立器使用 PDO 參數綁定來保護您的應用程式免受 SQL 注入攻擊。無需清理或消毒傳遞給查詢建立器的字串作為查詢綁定。
PDO 不支援綁定欄位名稱。因此,您絕不應允許使用者輸入來決定查詢引用的欄位名稱,包括「order by」欄位。
執行資料庫查詢
從表格中檢索所有列
您可以使用 DB
facade 提供的 table
方法來開始查詢。table
方法會為給定的表格返回一個流暢的查詢建立器實例,讓您可以將更多約束鏈接到查詢,然後最終使用 get
方法檢索查詢結果
1<?php 2 3namespace App\Http\Controllers; 4 5use Illuminate\Support\Facades\DB; 6use Illuminate\View\View; 7 8class UserController extends Controller 9{10 /**11 * Show a list of all of the application's users.12 */13 public function index(): View14 {15 $users = DB::table('users')->get();16 17 return view('user.index', ['users' => $users]);18 }19}
get
方法會返回一個 Illuminate\Support\Collection
實例,其中包含查詢結果,每個結果都是 PHP stdClass
物件的實例。您可以通過將欄位作為物件的屬性來存取每個欄位的值
1use Illuminate\Support\Facades\DB;2 3$users = DB::table('users')->get();4 5foreach ($users as $user) {6 echo $user->name;7}
Laravel 集合提供了各種非常強大的方法來映射和縮減數據。有關 Laravel 集合的更多資訊,請查看集合文件。
從表格中檢索單行/列
如果您只需要從資料庫表格中檢索單行,您可以使用 DB
facade 的 first
方法。此方法將返回單個 stdClass
物件
1$user = DB::table('users')->where('name', 'John')->first();2 3return $user->email;
如果您想從資料庫表格中檢索單行,但如果找不到匹配的行,則拋出 Illuminate\Database\RecordNotFoundException
,您可以使用 firstOrFail
方法。如果 RecordNotFoundException
未被捕獲,則會自動將 404 HTTP 回應發送回客户端
1$user = DB::table('users')->where('name', 'John')->firstOrFail();
如果您不需要整行,您可以使用 value
方法從記錄中提取單個值。此方法將直接返回欄位的值
1$email = DB::table('users')->where('name', 'John')->value('email');
要按其 id
欄位值檢索單行,請使用 find
方法
1$user = DB::table('users')->find(3);
檢索欄位值的列表
如果您想檢索一個 Illuminate\Support\Collection
實例,其中包含單個欄位的值,您可以使用 pluck
方法。在本例中,我們將檢索使用者標題的集合
1use Illuminate\Support\Facades\DB;2 3$titles = DB::table('users')->pluck('title');4 5foreach ($titles as $title) {6 echo $title;7}
您可以通過向 pluck
方法提供第二個參數來指定結果集合應使用的鍵的欄位
1$titles = DB::table('users')->pluck('title', 'name');2 3foreach ($titles as $name => $title) {4 echo $title;5}
分塊取得結果
如果您需要處理數千個資料庫記錄,請考慮使用 DB
facade 提供的 chunk
方法。此方法一次檢索一小塊結果,並將每個塊饋送到閉包以進行處理。例如,讓我們一次檢索 100 條記錄的整個 users
表格塊
1use Illuminate\Support\Collection;2use Illuminate\Support\Facades\DB;3 4DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {5 foreach ($users as $user) {6 // ...7 }8});
您可以通過從閉包返回 false
來停止處理進一步的塊
1DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {2 // Process the records...3 4 return false;5});
如果您在分塊結果時更新資料庫記錄,您的分塊結果可能會以意想不到的方式更改。如果您計劃在分塊時更新檢索到的記錄,最好始終改用 chunkById
方法。此方法將根據記錄的主鍵自動分頁結果
1DB::table('users')->where('active', false)2 ->chunkById(100, function (Collection $users) {3 foreach ($users as $user) {4 DB::table('users')5 ->where('id', $user->id)6 ->update(['active' => true]);7 }8 });
由於 chunkById
和 lazyById
方法將它們自己的「where」條件添加到正在執行的查詢中,因此您通常應該在閉包中邏輯分組您自己的條件
1DB::table('users')->where(function ($query) {2 $query->where('credits', 1)->orWhere('credits', 2);3})->chunkById(100, function (Collection $users) {4 foreach ($users as $user) {5 DB::table('users')6 ->where('id', $user->id)7 ->update(['credits' => 3]);8 }9});
在塊回調中更新或刪除記錄時,對主鍵或外鍵的任何更改都可能影響塊查詢。這可能會導致記錄未包含在分塊結果中。
延遲串流處理結果
lazy
方法的工作方式與 chunk
方法 類似,因為它以塊執行查詢。但是,lazy()
方法不是將每個塊傳遞到回調中,而是返回一個 LazyCollection
,讓您可以將結果作為單個流進行交互
1use Illuminate\Support\Facades\DB;2 3DB::table('users')->orderBy('id')->lazy()->each(function (object $user) {4 // ...5});
再次強調,如果您計劃在迭代檢索到的記錄時更新它們,最好改用 lazyById
或 lazyByIdDesc
方法。這些方法將根據記錄的主鍵自動分頁結果
1DB::table('users')->where('active', false)2 ->lazyById()->each(function (object $user) {3 DB::table('users')4 ->where('id', $user->id)5 ->update(['active' => true]);6 });
在迭代記錄時更新或刪除記錄時,對主鍵或外鍵的任何更改都可能影響塊查詢。這可能會導致記錄未包含在結果中。
聚合
查詢建立器還提供了多種方法來檢索聚合值,例如 count
、max
、min
、avg
和 sum
。您可以在建構查詢後調用這些方法中的任何一種
1use Illuminate\Support\Facades\DB;2 3$users = DB::table('users')->count();4 5$price = DB::table('orders')->max('price');
當然,您可以將這些方法與其他子句結合使用,以微調聚合值的計算方式
1$price = DB::table('orders')2 ->where('finalized', 1)3 ->avg('price');
確定記錄是否存在
您可以使用 exists
和 doesntExist
方法,而不是使用 count
方法來確定是否存在任何與查詢約束匹配的記錄
1if (DB::table('orders')->where('finalized', 1)->exists()) {2 // ...3}4 5if (DB::table('orders')->where('finalized', 1)->doesntExist()) {6 // ...7}
Select 語句
指定 Select 子句
您可能並不總是想從資料庫表格中選擇所有欄位。使用 select
方法,您可以為查詢指定自訂的「select」子句
1use Illuminate\Support\Facades\DB;2 3$users = DB::table('users')4 ->select('name', 'email as user_email')5 ->get();
distinct
方法允許您強制查詢返回不同的結果
1$users = DB::table('users')->distinct()->get();
如果您已經有一個查詢建立器實例,並且希望將欄位添加到其現有的 select 子句中,則可以使用 addSelect
方法
1$query = DB::table('users')->select('name');2 3$users = $query->addSelect('age')->get();
原始表達式
有時您可能需要將任意字串插入查詢中。要建立原始字串表達式,您可以使用 DB
facade 提供的 raw
方法
1$users = DB::table('users')2 ->select(DB::raw('count(*) as user_count, status'))3 ->where('status', '<>', 1)4 ->groupBy('status')5 ->get();
原始語句將作為字串注入到查詢中,因此您應格外小心,避免建立 SQL 注入漏洞。
原始方法
除了使用 DB::raw
方法外,您還可以使用以下方法將原始表達式插入到查詢的各個部分。請記住,Laravel 無法保證任何使用原始表達式的查詢都能防止 SQL 注入漏洞。
selectRaw
selectRaw
方法可以用於代替 addSelect(DB::raw(/* ... */))
。此方法接受一個可選的綁定陣列作為其第二個參數
1$orders = DB::table('orders')2 ->selectRaw('price * ? as price_with_tax', [1.0825])3 ->get();
whereRaw / orWhereRaw
whereRaw
和 orWhereRaw
方法可用於將原始 "where" 子句注入到您的查詢中。這些方法接受一個可選的綁定陣列作為它們的第二個參數
1$orders = DB::table('orders')2 ->whereRaw('price > IF(state = "TX", ?, 100)', [200])3 ->get();
havingRaw / orHavingRaw
havingRaw
和 orHavingRaw
方法可用於提供原始字串作為 "having" 子句的值。這些方法接受一個可選的綁定陣列作為它們的第二個參數
1$orders = DB::table('orders')2 ->select('department', DB::raw('SUM(price) as total_sales'))3 ->groupBy('department')4 ->havingRaw('SUM(price) > ?', [2500])5 ->get();
orderByRaw
orderByRaw
方法可用於提供原始字串作為 "order by" 子句的值
1$orders = DB::table('orders')2 ->orderByRaw('updated_at - created_at DESC')3 ->get();
groupByRaw
groupByRaw
方法可用於提供原始字串作為 group by
子句的值
1$orders = DB::table('orders')2 ->select('city', 'state')3 ->groupByRaw('city, state')4 ->get();
Joins(聯結)
Inner Join Clause
查詢建構器也可以用於將 join 子句添加到您的查詢中。要執行基本的 "inner join",您可以對查詢建構器實例使用 join
方法。傳遞給 join
方法的第一個參數是您需要加入的表格名稱,而其餘參數則指定 join 的欄位約束條件。您甚至可以在單個查詢中加入多個表格
1use Illuminate\Support\Facades\DB;2 3$users = DB::table('users')4 ->join('contacts', 'users.id', '=', 'contacts.user_id')5 ->join('orders', 'users.id', '=', 'orders.user_id')6 ->select('users.*', 'contacts.phone', 'orders.price')7 ->get();
Left Join / Right Join Clause
如果您想要執行 "left join" 或 "right join" 而不是 "inner join",請使用 leftJoin
或 rightJoin
方法。這些方法與 join
方法具有相同的簽名
1$users = DB::table('users')2 ->leftJoin('posts', 'users.id', '=', 'posts.user_id')3 ->get();4 5$users = DB::table('users')6 ->rightJoin('posts', 'users.id', '=', 'posts.user_id')7 ->get();
Cross Join Clause
您可以使用 crossJoin
方法來執行 "cross join"。Cross join 會在第一個表格和被加入的表格之間產生笛卡爾積
1$sizes = DB::table('sizes')2 ->crossJoin('colors')3 ->get();
Advanced Join Clauses
您還可以指定更進階的 join 子句。要開始使用,請將閉包作為第二個參數傳遞給 join
方法。閉包將接收一個 Illuminate\Database\Query\JoinClause
實例,您可以使用它來指定 "join" 子句的約束條件
1DB::table('users')2 ->join('contacts', function (JoinClause $join) {3 $join->on('users.id', '=', 'contacts.user_id')->orOn(/* ... */);4 })5 ->get();
如果您想在您的 join 中使用 "where" 子句,您可以使用 JoinClause
實例提供的 where
和 orWhere
方法。這些方法不是比較兩個欄位,而是將欄位與值進行比較
1DB::table('users')2 ->join('contacts', function (JoinClause $join) {3 $join->on('users.id', '=', 'contacts.user_id')4 ->where('contacts.user_id', '>', 5);5 })6 ->get();
Subquery Joins
您可以使用 joinSub
、leftJoinSub
和 rightJoinSub
方法將查詢加入到子查詢。這些方法中的每一個都接收三個參數:子查詢、其表格別名和定義相關欄位的閉包。在此範例中,我們將檢索使用者集合,其中每個使用者記錄也包含使用者最近發布的部落格文章的 created_at
時間戳記
1$latestPosts = DB::table('posts')2 ->select('user_id', DB::raw('MAX(created_at) as last_post_created_at'))3 ->where('is_published', true)4 ->groupBy('user_id');5 6$users = DB::table('users')7 ->joinSub($latestPosts, 'latest_posts', function (JoinClause $join) {8 $join->on('users.id', '=', 'latest_posts.user_id');9 })->get();
Lateral Joins
PostgreSQL、MySQL >= 8.0.14 和 SQL Server 目前支援 Lateral join。
您可以使用 joinLateral
和 leftJoinLateral
方法來執行帶有子查詢的 "lateral join"。這些方法中的每一個都接收兩個參數:子查詢及其表格別名。join 條件應在給定子查詢的 where
子句中指定。Lateral join 會針對每一列進行評估,並且可以引用子查詢之外的欄位。
在此範例中,我們將檢索使用者集合以及使用者最近的三篇部落格文章。每個使用者最多可以在結果集中產生三列:每篇最近的部落格文章各一列。join 條件是在子查詢中使用 whereColumn
子句指定的,該子句引用目前的使用者列
1$latestPosts = DB::table('posts')2 ->select('id as post_id', 'title as post_title', 'created_at as post_created_at')3 ->whereColumn('user_id', 'users.id')4 ->orderBy('created_at', 'desc')5 ->limit(3);6 7$users = DB::table('users')8 ->joinLateral($latestPosts, 'latest_posts')9 ->get();
Unions(聯集)
查詢建構器還提供了一種方便的方法來 "union" 兩個或多個查詢。例如,您可以建立一個初始查詢,並使用 union
方法將其與更多查詢聯合起來
1use Illuminate\Support\Facades\DB;2 3$first = DB::table('users')4 ->whereNull('first_name');5 6$users = DB::table('users')7 ->whereNull('last_name')8 ->union($first)9 ->get();
除了 union
方法之外,查詢建構器還提供了 unionAll
方法。使用 unionAll
方法組合的查詢將不會移除重複的結果。unionAll
方法具有與 union
方法相同的方法簽名。
基本 Where 條件子句
Where 條件子句
您可以使用查詢建構器的 where
方法將 "where" 子句添加到查詢中。對 where
方法最基本的呼叫需要三個參數。第一個參數是欄位的名稱。第二個參數是運算子,可以是資料庫支援的任何運算子。第三個參數是要與欄位值比較的數值。
例如,以下查詢檢索 votes
欄位的值等於 100
且 age
欄位的值大於 35
的使用者
1$users = DB::table('users')2 ->where('votes', '=', 100)3 ->where('age', '>', 35)4 ->get();
為了方便起見,如果您想驗證欄位是否 =
於給定的值,您可以將該值作為第二個參數傳遞給 where
方法。Laravel 將假設您想要使用 =
運算子
1$users = DB::table('users')->where('votes', 100)->get();
如先前所述,您可以使用資料庫系統支援的任何運算子
1$users = DB::table('users') 2 ->where('votes', '>=', 100) 3 ->get(); 4 5$users = DB::table('users') 6 ->where('votes', '<>', 100) 7 ->get(); 8 9$users = DB::table('users')10 ->where('name', 'like', 'T%')11 ->get();
您也可以將條件陣列傳遞給 where
函數。陣列的每個元素都應該是一個陣列,其中包含通常傳遞給 where
方法的三個參數
1$users = DB::table('users')->where([2 ['status', '=', '1'],3 ['subscribed', '<>', '1'],4])->get();
PDO 不支援綁定欄位名稱。因此,您絕不應允許使用者輸入來決定查詢引用的欄位名稱,包括「order by」欄位。
在字串與數字的比較中,MySQL 和 MariaDB 會自動將字串類型轉換為整數。在此過程中,非數字字串會被轉換為 0
,這可能會導致意外的結果。例如,如果您的表格有一個 secret
欄位,其值為 aaa
,並且您執行 User::where('secret', 0)
,則將會返回該列。為了避免這種情況,請確保在查詢中使用所有值之前,將它們的類型轉換為適當的類型。
Or Where 條件子句
當鏈式呼叫查詢建構器的 where
方法時,"where" 子句將使用 and
運算子連接在一起。但是,您可以使用 orWhere
方法使用 or
運算子將子句連接到查詢。orWhere
方法接受與 where
方法相同的參數
1$users = DB::table('users')2 ->where('votes', '>', 100)3 ->orWhere('name', 'John')4 ->get();
如果您需要在括號內對 "or" 條件進行分組,您可以將閉包作為第一個參數傳遞給 orWhere
方法
1$users = DB::table('users')2 ->where('votes', '>', 100)3 ->orWhere(function (Builder $query) {4 $query->where('name', 'Abigail')5 ->where('votes', '>', 50);6 })7 ->get();
上面的範例將產生以下 SQL
1select * from users where votes > 100 or (name = 'Abigail' and votes > 50)
為了避免在應用全域作用域時發生意外行為,您應該始終將 orWhere
呼叫分組。
Where Not 條件子句
whereNot
和 orWhereNot
方法可用於否定給定的一組查詢約束條件。例如,以下查詢排除正在促銷或價格低於十元的產品
1$products = DB::table('products')2 ->whereNot(function (Builder $query) {3 $query->where('clearance', true)4 ->orWhere('price', '<', 10);5 })6 ->get();
Where Any / All / None 條件子句
有時您可能需要將相同的查詢約束條件應用於多個欄位。例如,您可能想要檢索給定列表中任何欄位 LIKE
給定值的所有記錄。您可以使用 whereAny
方法來完成此操作
1$users = DB::table('users')2 ->where('active', true)3 ->whereAny([4 'name',5 'email',6 'phone',7 ], 'like', 'Example%')8 ->get();
上面的查詢將產生以下 SQL
1SELECT *2FROM users3WHERE active = true AND (4 name LIKE 'Example%' OR5 email LIKE 'Example%' OR6 phone LIKE 'Example%'7)
同樣地,whereAll
方法可用於檢索給定欄位的所有欄位都符合給定約束條件的記錄
1$posts = DB::table('posts')2 ->where('published', true)3 ->whereAll([4 'title',5 'content',6 ], 'like', '%Laravel%')7 ->get();
上面的查詢將產生以下 SQL
1SELECT *2FROM posts3WHERE published = true AND (4 title LIKE '%Laravel%' AND5 content LIKE '%Laravel%'6)
whereNone
方法可用於檢索給定欄位中沒有任何欄位符合給定約束條件的記錄
1$posts = DB::table('albums')2 ->where('published', true)3 ->whereNone([4 'title',5 'lyrics',6 'tags',7 ], 'like', '%explicit%')8 ->get();
上面的查詢將產生以下 SQL
1SELECT *2FROM albums3WHERE published = true AND NOT (4 title LIKE '%explicit%' OR5 lyrics LIKE '%explicit%' OR6 tags LIKE '%explicit%'7)
JSON Where 條件子句
Laravel 也支援在提供 JSON 欄位類型支援的資料庫上查詢 JSON 欄位類型。目前,這包括 MariaDB 10.3+、MySQL 8.0+、PostgreSQL 12.0+、SQL Server 2017+ 和 SQLite 3.39.0+。要查詢 JSON 欄位,請使用 ->
運算子
1$users = DB::table('users')2 ->where('preferences->dining->meal', 'salad')3 ->get();
您可以使用 whereJsonContains
來查詢 JSON 陣列
1$users = DB::table('users')2 ->whereJsonContains('options->languages', 'en')3 ->get();
如果您的應用程式使用 MariaDB、MySQL 或 PostgreSQL 資料庫,您可以將值陣列傳遞給 whereJsonContains
方法
1$users = DB::table('users')2 ->whereJsonContains('options->languages', ['en', 'de'])3 ->get();
您可以使用 whereJsonLength
方法按長度查詢 JSON 陣列
1$users = DB::table('users')2 ->whereJsonLength('options->languages', 0)3 ->get();4 5$users = DB::table('users')6 ->whereJsonLength('options->languages', '>', 1)7 ->get();
其他 Where 條件子句
whereLike / orWhereLike / whereNotLike / orWhereNotLike
whereLike
方法允許您將 "LIKE" 子句添加到查詢中以進行模式比對。這些方法提供了一種與資料庫無關的方式來執行字串比對查詢,並且能夠切換區分大小寫。預設情況下,字串比對是不區分大小寫的
1$users = DB::table('users')2 ->whereLike('name', '%John%')3 ->get();
您可以通過 caseSensitive
參數啟用區分大小寫的搜尋
1$users = DB::table('users')2 ->whereLike('name', '%John%', caseSensitive: true)3 ->get();
orWhereLike
方法允許您新增帶有 LIKE 條件的 "or" 子句
1$users = DB::table('users')2 ->where('votes', '>', 100)3 ->orWhereLike('name', '%John%')4 ->get();
whereNotLike
方法允許您新增 "NOT LIKE" 子句到您的查詢中
1$users = DB::table('users')2 ->whereNotLike('name', '%John%')3 ->get();
同樣地,您可以使用 orWhereNotLike
來新增帶有 NOT LIKE 條件的 "or" 子句
1$users = DB::table('users')2 ->where('votes', '>', 100)3 ->orWhereNotLike('name', '%John%')4 ->get();
SQL Server 目前不支援 whereLike
區分大小寫的搜尋選項。
whereIn / whereNotIn / orWhereIn / orWhereNotIn
whereIn
方法驗證給定欄位的值是否包含在給定陣列中
1$users = DB::table('users')2 ->whereIn('id', [1, 2, 3])3 ->get();
whereNotIn
方法驗證給定欄位的值是否未包含在給定陣列中
1$users = DB::table('users')2 ->whereNotIn('id', [1, 2, 3])3 ->get();
您也可以提供查詢物件作為 whereIn
方法的第二個參數
1$activeUsers = DB::table('users')->select('id')->where('is_active', 1);2 3$users = DB::table('comments')4 ->whereIn('user_id', $activeUsers)5 ->get();
上面的範例將產生以下 SQL
1select * from comments where user_id in (2 select id3 from users4 where is_active = 15)
如果您要將大量的整數綁定陣列添加到您的查詢中,則可以使用 whereIntegerInRaw
或 whereIntegerNotInRaw
方法來大幅減少記憶體使用量。
whereBetween / orWhereBetween
whereBetween
方法驗證欄位的值是否在兩個值之間
1$users = DB::table('users')2 ->whereBetween('votes', [1, 100])3 ->get();
whereNotBetween / orWhereNotBetween
whereNotBetween
方法驗證欄位的值是否在兩個值之外
1$users = DB::table('users')2 ->whereNotBetween('votes', [1, 100])3 ->get();
whereBetweenColumns / whereNotBetweenColumns / orWhereBetweenColumns / orWhereNotBetweenColumns
whereBetweenColumns
方法驗證欄位的值是否在同一表格列中兩個欄位的值之間
1$patients = DB::table('patients')2 ->whereBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight'])3 ->get();
whereNotBetweenColumns
方法驗證欄位的值是否在同一表格列中兩個欄位的值之外
1$patients = DB::table('patients')2 ->whereNotBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight'])3 ->get();
whereNull / whereNotNull / orWhereNull / orWhereNotNull
whereNull
方法驗證給定欄位的值是否為 NULL
1$users = DB::table('users')2 ->whereNull('updated_at')3 ->get();
whereNotNull
方法驗證欄位的值是否不為 NULL
1$users = DB::table('users')2 ->whereNotNull('updated_at')3 ->get();
whereDate / whereMonth / whereDay / whereYear / whereTime
whereDate
方法可用於將欄位的值與日期進行比較
1$users = DB::table('users')2 ->whereDate('created_at', '2016-12-31')3 ->get();
whereMonth
方法可用於將欄位的值與特定月份進行比較
1$users = DB::table('users')2 ->whereMonth('created_at', '12')3 ->get();
whereDay
方法可用於將欄位的值與月份的特定日期進行比較
1$users = DB::table('users')2 ->whereDay('created_at', '31')3 ->get();
whereYear
方法可用於將欄位的值與特定年份進行比較
1$users = DB::table('users')2 ->whereYear('created_at', '2016')3 ->get();
whereTime
方法可用於將欄位的值與特定時間進行比較
1$users = DB::table('users')2 ->whereTime('created_at', '=', '11:20:45')3 ->get();
wherePast / whereFuture / whereToday / whereBeforeToday / whereAfterToday
wherePast
和 whereFuture
方法可用於判斷欄位的值是在過去還是未來
1$invoices = DB::table('invoices')2 ->wherePast('due_at')3 ->get();4 5$invoices = DB::table('invoices')6 ->whereFuture('due_at')7 ->get();
whereNowOrPast
和 whereNowOrFuture
方法可用於判斷欄位的值是在過去還是未來,包括目前日期和時間
1$invoices = DB::table('invoices')2 ->whereNowOrPast('due_at')3 ->get();4 5$invoices = DB::table('invoices')6 ->whereNowOrFuture('due_at')7 ->get();
whereToday
、whereBeforeToday
和 whereAfterToday
方法可用於判斷欄位的值分別是今天、今天之前還是今天之後
1$invoices = DB::table('invoices') 2 ->whereToday('due_at') 3 ->get(); 4 5$invoices = DB::table('invoices') 6 ->whereBeforeToday('due_at') 7 ->get(); 8 9$invoices = DB::table('invoices')10 ->whereAfterToday('due_at')11 ->get();
同樣地,whereTodayOrBefore
和 whereTodayOrAfter
方法可用於判斷欄位的值是在今天之前還是今天之後,包括今天的日期
1$invoices = DB::table('invoices')2 ->whereTodayOrBefore('due_at')3 ->get();4 5$invoices = DB::table('invoices')6 ->whereTodayOrAfter('due_at')7 ->get();
whereColumn / orWhereColumn
whereColumn
方法可用於驗證兩個欄位是否相等
1$users = DB::table('users')2 ->whereColumn('first_name', 'last_name')3 ->get();
您也可以將比較運算子傳遞給 whereColumn
方法
1$users = DB::table('users')2 ->whereColumn('updated_at', '>', 'created_at')3 ->get();
您也可以將欄位比較陣列傳遞給 whereColumn
方法。這些條件將使用 and
運算子連接
1$users = DB::table('users')2 ->whereColumn([3 ['first_name', '=', 'last_name'],4 ['updated_at', '>', 'created_at'],5 ])->get();
邏輯分組
有時您可能需要在括號內對多個 "where" 子句進行分組,以實現查詢所需的邏輯分組。事實上,為了避免意外的查詢行為,您通常應該始終在括號中分組對 orWhere
方法的呼叫。為了實現這一點,您可以將閉包傳遞給 where
方法
1$users = DB::table('users')2 ->where('name', '=', 'John')3 ->where(function (Builder $query) {4 $query->where('votes', '>', 100)5 ->orWhere('title', '=', 'Admin');6 })7 ->get();
如您所見,將閉包傳遞到 where
方法中會指示查詢建構器開始一個約束條件組。閉包將接收一個查詢建構器實例,您可以使用它來設定應包含在括號組中的約束條件。上面的範例將產生以下 SQL
1select * from users where name = 'John' and (votes > 100 or title = 'Admin')
為了避免在應用全域作用域時發生意外行為,您應該始終將 orWhere
呼叫分組。
進階 Where 條件子句
Where Exists 條件子句
whereExists
方法允許您編寫 "where exists" SQL 子句。whereExists
方法接受一個閉包,該閉包將接收一個查詢建構器實例,允許您定義應放置在 "exists" 子句內部的查詢
1$users = DB::table('users')2 ->whereExists(function (Builder $query) {3 $query->select(DB::raw(1))4 ->from('orders')5 ->whereColumn('orders.user_id', 'users.id');6 })7 ->get();
或者,您可以將查詢物件傳遞給 whereExists
方法,而不是閉包
1$orders = DB::table('orders')2 ->select(DB::raw(1))3 ->whereColumn('orders.user_id', 'users.id');4 5$users = DB::table('users')6 ->whereExists($orders)7 ->get();
上面的兩個範例都將產生以下 SQL
1select * from users2where exists (3 select 14 from orders5 where orders.user_id = users.id6)
子查詢 Where 條件子句
有時您可能需要建構一個 "where" 子句,該子句將子查詢的結果與給定值進行比較。您可以通過將閉包和值傳遞給 where
方法來完成此操作。例如,以下查詢將檢索所有具有給定類型的最近 "membership" 的使用者;
1use App\Models\User; 2use Illuminate\Database\Query\Builder; 3 4$users = User::where(function (Builder $query) { 5 $query->select('type') 6 ->from('membership') 7 ->whereColumn('membership.user_id', 'users.id') 8 ->orderByDesc('membership.start_date') 9 ->limit(1);10}, 'Pro')->get();
或者,您可能需要建構一個 "where" 子句,該子句將欄位與子查詢的結果進行比較。您可以通過將欄位、運算子和閉包傳遞給 where
方法來完成此操作。例如,以下查詢將檢索所有金額小於平均值的收入記錄;
1use App\Models\Income;2use Illuminate\Database\Query\Builder;3 4$incomes = Income::where('amount', '<', function (Builder $query) {5 $query->selectRaw('avg(i.amount)')->from('incomes as i');6})->get();
全文 Where 條件子句
MariaDB、MySQL 和 PostgreSQL 目前支援全文 where 子句。
whereFullText
和 orWhereFullText
方法可用於將全文 "where" 子句添加到具有全文索引的欄位的查詢中。這些方法將由 Laravel 轉換為底層資料庫系統的適當 SQL。例如,對於使用 MariaDB 或 MySQL 的應用程式,將會產生 MATCH AGAINST
子句
1$users = DB::table('users')2 ->whereFullText('bio', 'web developer')3 ->get();
排序、分組、限制和偏移
排序
orderBy
方法
orderBy
方法允許您按給定欄位對查詢結果進行排序。orderBy
方法接受的第一個參數應該是您希望排序依據的欄位,而第二個參數決定排序的方向,可以是 asc
或 desc
1$users = DB::table('users')2 ->orderBy('name', 'desc')3 ->get();
要按多個欄位排序,您可以根據需要多次調用 orderBy
1$users = DB::table('users')2 ->orderBy('name', 'desc')3 ->orderBy('email', 'asc')4 ->get();
latest
和 oldest
方法
latest
和 oldest
方法允許您輕鬆地按日期排序結果。預設情況下,結果將按表格的 created_at
欄位排序。或者,您可以傳遞您希望排序依據的欄位名稱
1$user = DB::table('users')2 ->latest()3 ->first();
Random Ordering
inRandomOrder
方法可用於隨機排序查詢結果。例如,您可以使用此方法來獲取隨機使用者
1$randomUser = DB::table('users')2 ->inRandomOrder()3 ->first();
Removing Existing Orderings
reorder
方法會移除先前已應用於查詢的所有 "order by" 子句
1$query = DB::table('users')->orderBy('name');2 3$unorderedUsers = $query->reorder()->get();
您可以在呼叫 reorder
方法時傳遞欄位和方向,以便移除所有現有的 "order by" 子句,並將全新的排序應用於查詢
1$query = DB::table('users')->orderBy('name');2 3$usersOrderedByEmail = $query->reorder('email', 'desc')->get();
分組
groupBy
和 having
方法
正如您可能預期的那樣,groupBy
和 having
方法可用於對查詢結果進行分組。having
方法的簽名與 where
方法的簽名相似
1$users = DB::table('users')2 ->groupBy('account_id')3 ->having('account_id', '>', 100)4 ->get();
您可以使用 havingBetween
方法來篩選特定範圍內的結果
1$report = DB::table('orders')2 ->selectRaw('count(id) as number_of_orders, customer_id')3 ->groupBy('customer_id')4 ->havingBetween('number_of_orders', [5, 15])5 ->get();
您可以將多個參數傳遞給 groupBy
方法,以依多個欄位進行分組
1$users = DB::table('users')2 ->groupBy('first_name', 'status')3 ->having('account_id', '>', 100)4 ->get();
若要建立更進階的 having
語句,請參閱 havingRaw
方法。
限制和偏移
skip
和 take
方法
您可以使用 skip
和 take
方法來限制查詢返回的結果數量,或略過查詢中指定數量的結果
1$users = DB::table('users')->skip(10)->take(5)->get();
或者,您可以使用 limit
和 offset
方法。這些方法的功能分別與 take
和 skip
方法等效
1$users = DB::table('users')2 ->offset(10)3 ->limit(5)4 ->get();
條件子句
有時您可能希望某些查詢子句根據另一個條件應用於查詢。例如,您可能只想在傳入的 HTTP 請求中存在給定的輸入值時才應用 where
語句。您可以使用 when
方法來完成此操作
1$role = $request->input('role');2 3$users = DB::table('users')4 ->when($role, function (Builder $query, string $role) {5 $query->where('role_id', $role);6 })7 ->get();
只有當第一個參數為 true
時,when
方法才會執行給定的閉包。如果第一個參數為 false
,則不會執行閉包。因此,在上面的範例中,只有當 role
欄位存在於傳入的請求中且評估為 true
時,才會調用給 when
方法的閉包。
您可以將另一個閉包作為第三個參數傳遞給 when
方法。只有當第一個參數評估為 false
時,才會執行此閉包。為了說明如何使用此功能,我們將使用它來配置查詢的預設排序
1$sortByVotes = $request->boolean('sort_by_votes');2 3$users = DB::table('users')4 ->when($sortByVotes, function (Builder $query, bool $sortByVotes) {5 $query->orderBy('votes');6 }, function (Builder $query) {7 $query->orderBy('name');8 })9 ->get();
Insert 語句
查詢建構器還提供了一個 insert
方法,可用於將記錄插入資料庫表格。insert
方法接受欄位名稱和值的陣列
1DB::table('users')->insert([3 'votes' => 04]);
您可以透過傳遞陣列的陣列一次插入多筆記錄。每個陣列代表應插入表格中的一筆記錄
1DB::table('users')->insert([4]);
insertOrIgnore
方法將在將記錄插入資料庫時忽略錯誤。使用此方法時,您應該注意,重複記錄錯誤將被忽略,並且其他類型的錯誤也可能被忽略,具體取決於資料庫引擎。例如,insertOrIgnore
將 繞過 MySQL 的嚴格模式
1DB::table('users')->insertOrIgnore([4]);
insertUsing
方法將使用子查詢來確定應插入的資料,同時將新記錄插入表格中
1DB::table('pruned_users')->insertUsing([2 'id', 'name', 'email', 'email_verified_at'3], DB::table('users')->select(4 'id', 'name', 'email', 'email_verified_at'5)->where('updated_at', '<=', now()->subMonth()));
自動遞增 ID
如果表格具有自動遞增 ID,請使用 insertGetId
方法插入記錄,然後檢索 ID
1$id = DB::table('users')->insertGetId(3);
當使用 PostgreSQL 時,insertGetId
方法預期自動遞增欄位的名稱為 id
。如果您想從不同的「序列」檢索 ID,您可以將欄位名稱作為第二個參數傳遞給 insertGetId
方法。
Upserts(更新插入)
upsert
方法將插入不存在的記錄,並使用您可能指定的新值更新已存在的記錄。該方法的第一個參數包含要插入或更新的值,而第二個參數列出唯一識別關聯表格中記錄的欄位。該方法的第三個也是最後一個參數是一個欄位陣列,如果資料庫中已存在相符的記錄,則應更新這些欄位
1DB::table('flights')->upsert(2 [3 ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],4 ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]5 ],6 ['departure', 'destination'],7 ['price']8);
在上面的範例中,Laravel 將嘗試插入兩筆記錄。如果已存在具有相同 departure
和 destination
欄位值的記錄,Laravel 將更新該記錄的 price
欄位。
除了 SQL Server 之外的所有資料庫都要求 upsert
方法的第二個參數中的欄位具有「主索引」或「唯一索引」。此外,MariaDB 和 MySQL 資料庫驅動程式會忽略 upsert
方法的第二個參數,並且始終使用表格的「主索引」和「唯一索引」來偵測現有記錄。
Update 語句
除了將記錄插入資料庫之外,查詢建構器還可以透過使用 update
方法來更新現有記錄。與 insert
方法類似,update
方法接受欄位和值對的陣列,指示要更新的欄位。update
方法返回受影響的列數。您可以使用 where
子句來約束 update
查詢
1$affected = DB::table('users')2 ->where('id', 1)3 ->update(['votes' => 1]);
更新或插入
有時您可能想要更新資料庫中現有的記錄,如果沒有相符的記錄則建立它。在這種情況下,可以使用 updateOrInsert
方法。updateOrInsert
方法接受兩個參數:一個用於查找記錄的條件陣列,以及一個欄位和值對的陣列,指示要更新的欄位。
updateOrInsert
方法將嘗試使用第一個參數的欄位和值對來定位相符的資料庫記錄。如果記錄存在,它將使用第二個參數中的值進行更新。如果找不到記錄,則會插入一個新記錄,其中包含兩個參數的合併屬性
1DB::table('users')2 ->updateOrInsert(4 ['votes' => '2']5 );
您可以為 updateOrInsert
方法提供一個閉包,以根據相符記錄的存在情況自訂更新或插入資料庫的屬性
1DB::table('users')->updateOrInsert( 2 ['user_id' => $user_id], 3 fn ($exists) => $exists ? [ 4 'name' => $data['name'], 5 'email' => $data['email'], 6 ] : [ 7 'name' => $data['name'], 8 'email' => $data['email'], 9 'marketable' => true,10 ],11);
更新 JSON 欄位
當更新 JSON 欄位時,您應該使用 ->
語法來更新 JSON 物件中的相應鍵。MariaDB 10.3+、MySQL 5.7+ 和 PostgreSQL 9.5+ 支援此操作
1$affected = DB::table('users')2 ->where('id', 1)3 ->update(['options->enabled' => true]);
遞增和遞減
查詢建構器還提供了方便的方法來遞增或遞減給定欄位的值。這兩種方法都至少接受一個參數:要修改的欄位。可以提供第二個參數來指定欄位應遞增或遞減的量
1DB::table('users')->increment('votes');2 3DB::table('users')->increment('votes', 5);4 5DB::table('users')->decrement('votes');6 7DB::table('users')->decrement('votes', 5);
如果需要,您也可以在遞增或遞減操作期間指定要更新的其他欄位
1DB::table('users')->increment('votes', 1, ['name' => 'John']);
此外,您可以使用 incrementEach
和 decrementEach
方法一次遞增或遞減多個欄位
1DB::table('users')->incrementEach([2 'votes' => 5,3 'balance' => 100,4]);
Delete 語句
查詢建構器的 delete
方法可用於從表格中刪除記錄。delete
方法返回受影響的列數。您可以透過在調用 delete
方法之前新增 "where" 子句來約束 delete
語句
1$deleted = DB::table('users')->delete();2 3$deleted = DB::table('users')->where('votes', '>', 100)->delete();
悲觀鎖定
查詢建構器還包含一些功能,可協助您在執行 select
語句時實現「悲觀鎖定」。若要使用「共享鎖定」執行語句,您可以調用 sharedLock
方法。共享鎖定可防止選定的列在您的交易提交之前被修改
1DB::table('users')2 ->where('votes', '>', 100)3 ->sharedLock()4 ->get();
或者,您可以使用 lockForUpdate
方法。「for update」鎖定可防止選定的記錄被修改或被另一個共享鎖定選取
1DB::table('users')2 ->where('votes', '>', 100)3 ->lockForUpdate()4 ->get();
雖然不是強制性的,但建議將悲觀鎖定包裝在 交易 中。這確保了檢索到的資料在整個操作完成之前在資料庫中保持不變。如果發生故障,交易將回滾任何變更並自動釋放鎖定
1DB::transaction(function () { 2 $sender = DB::table('users') 3 ->lockForUpdate() 4 ->find(1); 5 6 $receiver = DB::table('users') 7 ->lockForUpdate(); 8 ->find(2); 9 10 if ($sender->balance < 100) {11 throw new RuntimeException('Balance too low.');12 }13 14 DB::table('users')15 ->where('id', $sender->id)16 ->update([17 'balance' => $sender->balance - 10018 ]);19 20 DB::table('users')21 ->where('id', $receiver->id)22 ->update([23 'balance' => $receiver->balance + 10024 ]);25});
除錯
您可以在建構查詢時使用 dd
和 dump
方法來傾印目前的查詢綁定和 SQL。dd
方法將顯示偵錯資訊,然後停止執行請求。dump
方法將顯示偵錯資訊,但允許請求繼續執行
1DB::table('users')->where('votes', '>', 100)->dd();2 3DB::table('users')->where('votes', '>', 100)->dump();
可以調用 dumpRawSql
和 ddRawSql
方法來傾印查詢的 SQL,其中所有參數綁定都已正確替換
1DB::table('users')->where('votes', '>', 100)->dumpRawSql();2 3DB::table('users')->where('votes', '>', 100)->ddRawSql();