跳到內容

資料庫:查詢建立器

簡介

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(): View
14 {
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 });

由於 chunkByIdlazyById 方法將它們自己的「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});

再次強調,如果您計劃在迭代檢索到的記錄時更新它們,最好改用 lazyByIdlazyByIdDesc 方法。這些方法將根據記錄的主鍵自動分頁結果

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 });

在迭代記錄時更新或刪除記錄時,對主鍵或外鍵的任何更改都可能影響塊查詢。這可能會導致記錄未包含在結果中。

聚合

查詢建立器還提供了多種方法來檢索聚合值,例如 countmaxminavgsum。您可以在建構查詢後調用這些方法中的任何一種

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

確定記錄是否存在

您可以使用 existsdoesntExist 方法,而不是使用 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

whereRaworWhereRaw 方法可用於將原始 "where" 子句注入到您的查詢中。這些方法接受一個可選的綁定陣列作為它們的第二個參數

1$orders = DB::table('orders')
2 ->whereRaw('price > IF(state = "TX", ?, 100)', [200])
3 ->get();

havingRaw / orHavingRaw

havingRaworHavingRaw 方法可用於提供原始字串作為 "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",請使用 leftJoinrightJoin 方法。這些方法與 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 實例提供的 whereorWhere 方法。這些方法不是比較兩個欄位,而是將欄位與值進行比較

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

您可以使用 joinSubleftJoinSubrightJoinSub 方法將查詢加入到子查詢。這些方法中的每一個都接收三個參數:子查詢、其表格別名和定義相關欄位的閉包。在此範例中,我們將檢索使用者集合,其中每個使用者記錄也包含使用者最近發布的部落格文章的 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。

您可以使用 joinLateralleftJoinLateral 方法來執行帶有子查詢的 "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 欄位的值等於 100age 欄位的值大於 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 條件子句

whereNotorWhereNot 方法可用於否定給定的一組查詢約束條件。例如,以下查詢排除正在促銷或價格低於十元的產品

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 users
3WHERE active = true AND (
4 name LIKE 'Example%' OR
5 email LIKE 'Example%' OR
6 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 posts
3WHERE published = true AND (
4 title LIKE '%Laravel%' AND
5 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 albums
3WHERE published = true AND NOT (
4 title LIKE '%explicit%' OR
5 lyrics LIKE '%explicit%' OR
6 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 id
3 from users
4 where is_active = 1
5)

如果您要將大量的整數綁定陣列添加到您的查詢中,則可以使用 whereIntegerInRawwhereIntegerNotInRaw 方法來大幅減少記憶體使用量。

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

wherePastwhereFuture 方法可用於判斷欄位的值是在過去還是未來

1$invoices = DB::table('invoices')
2 ->wherePast('due_at')
3 ->get();
4 
5$invoices = DB::table('invoices')
6 ->whereFuture('due_at')
7 ->get();

whereNowOrPastwhereNowOrFuture 方法可用於判斷欄位的值是在過去還是未來,包括目前日期和時間

1$invoices = DB::table('invoices')
2 ->whereNowOrPast('due_at')
3 ->get();
4 
5$invoices = DB::table('invoices')
6 ->whereNowOrFuture('due_at')
7 ->get();

whereTodaywhereBeforeTodaywhereAfterToday 方法可用於判斷欄位的值分別是今天、今天之前還是今天之後

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

同樣地,whereTodayOrBeforewhereTodayOrAfter 方法可用於判斷欄位的值是在今天之前還是今天之後,包括今天的日期

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 users
2where exists (
3 select 1
4 from orders
5 where orders.user_id = users.id
6)

子查詢 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 子句。

whereFullTextorWhereFullText 方法可用於將全文 "where" 子句添加到具有全文索引的欄位的查詢中。這些方法將由 Laravel 轉換為底層資料庫系統的適當 SQL。例如,對於使用 MariaDB 或 MySQL 的應用程式,將會產生 MATCH AGAINST 子句

1$users = DB::table('users')
2 ->whereFullText('bio', 'web developer')
3 ->get();

排序、分組、限制和偏移

排序

orderBy 方法

orderBy 方法允許您按給定欄位對查詢結果進行排序。orderBy 方法接受的第一個參數應該是您希望排序依據的欄位,而第二個參數決定排序的方向,可以是 ascdesc

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

latestoldest 方法

latestoldest 方法允許您輕鬆地按日期排序結果。預設情況下,結果將按表格的 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();

分組

groupByhaving 方法

正如您可能預期的那樣,groupByhaving 方法可用於對查詢結果進行分組。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 方法。

限制和偏移

skiptake 方法

您可以使用 skiptake 方法來限制查詢返回的結果數量,或略過查詢中指定數量的結果

1$users = DB::table('users')->skip(10)->take(5)->get();

或者,您可以使用 limitoffset 方法。這些方法的功能分別與 takeskip 方法等效

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([
2 'email' => '[email protected]',
3 'votes' => 0
4]);

您可以透過傳遞陣列的陣列一次插入多筆記錄。每個陣列代表應插入表格中的一筆記錄

1DB::table('users')->insert([
2 ['email' => '[email protected]', 'votes' => 0],
3 ['email' => '[email protected]', 'votes' => 0],
4]);

insertOrIgnore 方法將在將記錄插入資料庫時忽略錯誤。使用此方法時,您應該注意,重複記錄錯誤將被忽略,並且其他類型的錯誤也可能被忽略,具體取決於資料庫引擎。例如,insertOrIgnore繞過 MySQL 的嚴格模式

1DB::table('users')->insertOrIgnore([
2 ['id' => 1, 'email' => '[email protected]'],
3 ['id' => 2, 'email' => '[email protected]'],
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(
2 ['email' => '[email protected]', 'votes' => 0]
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 將嘗試插入兩筆記錄。如果已存在具有相同 departuredestination 欄位值的記錄,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(
3 ['email' => '[email protected]', 'name' => 'John'],
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']);

此外,您可以使用 incrementEachdecrementEach 方法一次遞增或遞減多個欄位

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 - 100
18 ]);
19 
20 DB::table('users')
21 ->where('id', $receiver->id)
22 ->update([
23 'balance' => $receiver->balance + 100
24 ]);
25});

除錯

您可以在建構查詢時使用 dddump 方法來傾印目前的查詢綁定和 SQL。dd 方法將顯示偵錯資訊,然後停止執行請求。dump 方法將顯示偵錯資訊,但允許請求繼續執行

1DB::table('users')->where('votes', '>', 100)->dd();
2 
3DB::table('users')->where('votes', '>', 100)->dump();

可以調用 dumpRawSqlddRawSql 方法來傾印查詢的 SQL,其中所有參數綁定都已正確替換

1DB::table('users')->where('votes', '>', 100)->dumpRawSql();
2 
3DB::table('users')->where('votes', '>', 100)->ddRawSql();

Laravel 是最有效率的方式來
建構、部署和監控軟體。