跳至內容

資料庫:查詢建立器

簡介

Laravel 的資料庫查詢建立器提供了一個方便、流暢的介面,用於建立和執行資料庫查詢。它可用於在您的應用程式中執行大多數資料庫操作,並且可以完美地與 Laravel 所有支援的資料庫系統搭配使用。

Laravel 查詢建立器使用 PDO 參數綁定來保護您的應用程式免受 SQL 注入攻擊。無需清除或清理傳遞給查詢建立器的字串作為查詢綁定。

exclamation

PDO 不支援綁定資料行名稱。因此,您絕不應允許使用者輸入來決定查詢中引用的資料行名稱,包括「order by」資料行。

執行資料庫查詢

從表格擷取所有列

您可以使用 DB 外觀模式提供的 table 方法來開始查詢。table 方法會為給定的表格傳回流暢的查詢建立器實例,讓您可以將更多約束鏈結到查詢,然後使用 get 方法最終擷取查詢的結果

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
 
class UserController extends Controller
{
/**
* Show a list of all of the application's users.
*/
public function index(): View
{
$users = DB::table('users')->get();
 
return view('user.index', ['users' => $users]);
}
}

get 方法會傳回 Illuminate\Support\Collection 實例,其中包含查詢的結果,其中每個結果都是 PHP stdClass 物件的實例。您可以透過存取物件的屬性來存取每個資料行的值

use Illuminate\Support\Facades\DB;
 
$users = DB::table('users')->get();
 
foreach ($users as $user) {
echo $user->name;
}
lightbulb

Laravel 集合提供了各種非常強大的方法來對資料進行映射和縮減。如需更多關於 Laravel 集合的資訊,請查看集合文件

從表格擷取單一列/資料行

如果您只需要從資料庫表格擷取單一列,可以使用 DB 外觀模式的 first 方法。此方法會傳回單一 stdClass 物件

$user = DB::table('users')->where('name', 'John')->first();
 
return $user->email;

如果您想從資料庫表格擷取單一列,但如果找不到相符的列,則會擲回 Illuminate\Database\RecordNotFoundException,您可以使用 firstOrFail 方法。如果未捕獲 RecordNotFoundException,則會自動將 404 HTTP 回應傳回給用戶端

$user = DB::table('users')->where('name', 'John')->firstOrFail();

如果您不需要整列,可以使用 value 方法從記錄中擷取單一值。此方法會直接傳回資料行的值

$email = DB::table('users')->where('name', 'John')->value('email');

若要依據其 id 資料行值擷取單一列,請使用 find 方法

$user = DB::table('users')->find(3);

擷取資料行值的清單

如果您想要擷取包含單一資料行值的 Illuminate\Support\Collection 實例,您可以使用 pluck 方法。在此範例中,我們將擷取使用者標題的集合

use Illuminate\Support\Facades\DB;
 
$titles = DB::table('users')->pluck('title');
 
foreach ($titles as $title) {
echo $title;
}

您可以透過提供第二個參數給 pluck 方法來指定結果集合應使用的資料行作為其索引鍵

$titles = DB::table('users')->pluck('title', 'name');
 
foreach ($titles as $name => $title) {
echo $title;
}

分塊結果

如果您需要處理數千個資料庫記錄,請考慮使用 DB 外觀模式提供的 chunk 方法。此方法一次擷取一小部分結果,並將每個分塊饋送到一個閉包進行處理。例如,讓我們一次擷取 100 筆記錄,擷取整個 users 表格

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
 
DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {
foreach ($users as $user) {
// ...
}
});

您可以透過從閉包傳回 false 來停止處理其他分塊

DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {
// Process the records...
 
return false;
});

如果您在分塊結果時更新資料庫記錄,您的分塊結果可能會以意想不到的方式變更。如果您打算在分塊時更新擷取的記錄,最好始終使用 chunkById 方法。此方法會根據記錄的主索引鍵自動分頁結果

DB::table('users')->where('active', false)
->chunkById(100, function (Collection $users) {
foreach ($users as $user) {
DB::table('users')
->where('id', $user->id)
->update(['active' => true]);
}
});

由於 chunkByIdlazyById 方法會將它們自己的「where」條件新增至執行的查詢,您通常應該在閉包中邏輯分組您自己的條件

DB::table('users')->where(function ($query) {
$query->where('credits', 1)->orWhere('credits', 2);
})->chunkById(100, function (Collection $users) {
foreach ($users as $user) {
DB::table('users')
->where('id', $user->id)
->update(['credits' => 3]);
}
});
exclamation

在分塊回呼內更新或刪除記錄時,對主索引鍵或外部索引鍵的任何變更都可能會影響分塊查詢。這可能會導致記錄未包含在分塊的結果中。

惰性串流結果

lazy 方法的工作方式與 chunk 方法類似,它會以分塊的方式執行查詢。但是,lazy() 方法不是將每個分塊傳遞到回呼中,而是傳回 LazyCollection,讓您可以將結果視為單一串流進行互動

use Illuminate\Support\Facades\DB;
 
DB::table('users')->orderBy('id')->lazy()->each(function (object $user) {
// ...
});

再次說明,如果您打算在反覆查看擷取的記錄時更新它們,最好改為使用 lazyByIdlazyByIdDesc 方法。這些方法會根據記錄的主索引鍵自動分頁結果

DB::table('users')->where('active', false)
->lazyById()->each(function (object $user) {
DB::table('users')
->where('id', $user->id)
->update(['active' => true]);
});
exclamation

在反覆查看記錄時更新或刪除記錄時,對主索引鍵或外部索引鍵的任何變更都可能會影響分塊查詢。這可能會導致記錄未包含在結果中。

彙總

查詢建立器也提供了各種方法來擷取彙總值,如 countmaxminavgsum。您可以在建構查詢後呼叫任何這些方法

use Illuminate\Support\Facades\DB;
 
$users = DB::table('users')->count();
 
$price = DB::table('orders')->max('price');

當然,您可以將這些方法與其他子句結合使用,以微調彙總值的計算方式

$price = DB::table('orders')
->where('finalized', 1)
->avg('price');

判斷記錄是否存在

您可以使用 existsdoesntExist 方法,而不是使用 count 方法來判斷是否存在任何符合查詢限制的記錄

if (DB::table('orders')->where('finalized', 1)->exists()) {
// ...
}
 
if (DB::table('orders')->where('finalized', 1)->doesntExist()) {
// ...
}

Select 語句

指定 Select 子句

您可能不總是想要從資料庫表格中選取所有欄位。透過使用 select 方法,您可以為查詢指定自訂的 "select" 子句。

use Illuminate\Support\Facades\DB;
 
$users = DB::table('users')
->select('name', 'email as user_email')
->get();

distinct 方法允許您強制查詢傳回不重複的結果。

$users = DB::table('users')->distinct()->get();

如果您已經有一個查詢建構器實例,並且希望在其現有的 select 子句中新增一個欄位,您可以使用 addSelect 方法。

$query = DB::table('users')->select('name');
 
$users = $query->addSelect('age')->get();

原始運算式

有時您可能需要在查詢中插入任意字串。若要建立原始字串表達式,您可以使用 DB facade 提供的 raw 方法。

$users = DB::table('users')
->select(DB::raw('count(*) as user_count, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
exclamation

原始語句會以字串形式注入到查詢中,因此您應該非常小心,避免產生 SQL 注入漏洞。

原始方法

除了使用 DB::raw 方法之外,您也可以使用下列方法將原始表達式插入查詢的各個部分。請記住,Laravel 無法保證任何使用原始表達式的查詢都能防止 SQL 注入漏洞。

selectRaw

selectRaw 方法可以用來取代 addSelect(DB::raw(/* ... */))。此方法接受一個可選的綁定陣列作為其第二個參數。

$orders = DB::table('orders')
->selectRaw('price * ? as price_with_tax', [1.0825])
->get();

whereRaw / orWhereRaw

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

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

havingRaw / orHavingRaw

havingRaworHavingRaw 方法可用於提供原始字串作為 "having" 子句的值。這些方法接受一個可選的綁定陣列作為其第二個參數。

$orders = DB::table('orders')
->select('department', DB::raw('SUM(price) as total_sales'))
->groupBy('department')
->havingRaw('SUM(price) > ?', [2500])
->get();

orderByRaw

orderByRaw 方法可用於提供原始字串作為 "order by" 子句的值。

$orders = DB::table('orders')
->orderByRaw('updated_at - created_at DESC')
->get();

groupByRaw

groupByRaw 方法可用於提供原始字串作為 group by 子句的值。

$orders = DB::table('orders')
->select('city', 'state')
->groupByRaw('city, state')
->get();

聯結

內部聯結子句

查詢建構器也可用於將聯結子句新增到您的查詢中。若要執行基本的「內部聯結」,您可以在查詢建構器實例上使用 join 方法。傳遞給 join 方法的第一個參數是您需要聯結的表格名稱,而其餘參數則指定聯結的欄位約束。您甚至可以在單一查詢中聯結多個表格。

use Illuminate\Support\Facades\DB;
 
$users = DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.*', 'contacts.phone', 'orders.price')
->get();

左聯結 / 右聯結子句

如果您想要執行「左聯結」或「右聯結」而不是「內部聯結」,請使用 leftJoinrightJoin 方法。這些方法與 join 方法具有相同的簽名。

$users = DB::table('users')
->leftJoin('posts', 'users.id', '=', 'posts.user_id')
->get();
 
$users = DB::table('users')
->rightJoin('posts', 'users.id', '=', 'posts.user_id')
->get();

交叉聯結子句

您可以使用 crossJoin 方法執行「交叉聯結」。交叉聯結會在第一個表格和聯結的表格之間產生笛卡爾積。

$sizes = DB::table('sizes')
->crossJoin('colors')
->get();

進階聯結子句

您也可以指定更進階的聯結子句。若要開始,請將一個閉包作為第二個參數傳遞給 join 方法。該閉包將接收一個 Illuminate\Database\Query\JoinClause 實例,允許您指定「聯結」子句的約束。

DB::table('users')
->join('contacts', function (JoinClause $join) {
$join->on('users.id', '=', 'contacts.user_id')->orOn(/* ... */);
})
->get();

如果您想在您的聯結上使用 "where" 子句,您可以使用 JoinClause 實例提供的 whereorWhere 方法。這些方法不是比較兩個欄位,而是比較欄位與值。

DB::table('users')
->join('contacts', function (JoinClause $join) {
$join->on('users.id', '=', 'contacts.user_id')
->where('contacts.user_id', '>', 5);
})
->get();

子查詢聯結

您可以使用 joinSubleftJoinSubrightJoinSub 方法將查詢聯結到子查詢。這些方法中的每一個都接收三個參數:子查詢、其表格別名,以及定義相關欄位的閉包。在此範例中,我們將檢索一個使用者集合,其中每個使用者記錄也包含使用者最近發布的部落格文章的 created_at 時間戳記。

$latestPosts = DB::table('posts')
->select('user_id', DB::raw('MAX(created_at) as last_post_created_at'))
->where('is_published', true)
->groupBy('user_id');
 
$users = DB::table('users')
->joinSub($latestPosts, 'latest_posts', function (JoinClause $join) {
$join->on('users.id', '=', 'latest_posts.user_id');
})->get();

橫向聯結

exclamation

目前 PostgreSQL、MySQL >= 8.0.14 和 SQL Server 支援橫向聯結。

您可以使用 joinLateralleftJoinLateral 方法來執行與子查詢的「橫向聯結」。這些方法中的每一個都接收兩個參數:子查詢及其表格別名。聯結條件應在給定子查詢的 where 子句中指定。橫向聯結會針對每個資料列進行評估,並且可以參照子查詢外部的欄位。

在此範例中,我們將檢索一個使用者集合,以及使用者最近的三篇部落格文章。每個使用者可以在結果集中產生最多三列:每篇最近的部落格文章一列。聯結條件是在子查詢內使用 whereColumn 子句指定,參照目前的使用者列。

$latestPosts = DB::table('posts')
->select('id as post_id', 'title as post_title', 'created_at as post_created_at')
->whereColumn('user_id', 'users.id')
->orderBy('created_at', 'desc')
->limit(3);
 
$users = DB::table('users')
->joinLateral($latestPosts, 'latest_posts')
->get();

聯集

查詢建構器也提供了一種方便的方法來「聯合」兩個或多個查詢。例如,您可以建立一個初始查詢,並使用 union 方法將其與更多查詢聯合。

use Illuminate\Support\Facades\DB;
 
$first = DB::table('users')
->whereNull('first_name');
 
$users = DB::table('users')
->whereNull('last_name')
->union($first)
->get();

除了 union 方法之外,查詢建構器還提供了一個 unionAll 方法。使用 unionAll 方法組合的查詢不會刪除重複的結果。 unionAll 方法具有與 union 方法相同的簽名。

基本 Where 子句

Where 子句

您可以使用查詢建構器的 where 方法將 "where" 子句新增到查詢中。對 where 方法的最基本呼叫需要三個參數。第一個參數是欄位的名稱。第二個參數是運算子,可以是資料庫支援的任何運算子。第三個參數是要與欄位值比較的值。

例如,以下查詢會檢索 votes 欄位的值等於 100age 欄位的值大於 35 的使用者。

$users = DB::table('users')
->where('votes', '=', 100)
->where('age', '>', 35)
->get();

為方便起見,如果您想要驗證欄位是否 = 於給定值,您可以將該值作為第二個參數傳遞給 where 方法。Laravel 會假設您想要使用 = 運算子。

$users = DB::table('users')->where('votes', 100)->get();

如先前所述,您可以使用資料庫系統支援的任何運算子。

$users = DB::table('users')
->where('votes', '>=', 100)
->get();
 
$users = DB::table('users')
->where('votes', '<>', 100)
->get();
 
$users = DB::table('users')
->where('name', 'like', 'T%')
->get();

您也可以將條件陣列傳遞給 where 函式。陣列的每個元素都應該是一個包含通常傳遞給 where 方法的三個參數的陣列。

$users = DB::table('users')->where([
['status', '=', '1'],
['subscribed', '<>', '1'],
])->get();
exclamation

PDO 不支援綁定資料行名稱。因此,您絕不應允許使用者輸入來決定查詢中引用的資料行名稱,包括「order by」資料行。

exclamation

MySQL 和 MariaDB 會在字串數字比較中自動將字串轉換為整數。在此過程中,非數字字串會轉換為 0,這可能會導致非預期的結果。例如,如果您的表格有一個值為 aaasecret 欄位,並且您執行 User::where('secret', 0),則會傳回該資料列。若要避免這種情況,請確保所有值在用於查詢之前都已轉換為其適當的類型。

Or Where 子句

當將對查詢建構器的 where 方法的呼叫鏈接在一起時,"where" 子句將使用 and 運算子連接在一起。但是,您可以使用 orWhere 方法來使用 or 運算子將子句連接到查詢。 orWhere 方法接受與 where 方法相同的參數。

$users = DB::table('users')
->where('votes', '>', 100)
->orWhere('name', 'John')
->get();

如果您需要在括號內分組 "or" 條件,您可以將閉包作為第一個參數傳遞給 orWhere 方法。

$users = DB::table('users')
->where('votes', '>', 100)
->orWhere(function (Builder $query) {
$query->where('name', 'Abigail')
->where('votes', '>', 50);
})
->get();

上面的範例將產生以下 SQL

select * from users where votes > 100 or (name = 'Abigail' and votes > 50)
exclamation

您應該始終將 orWhere 呼叫分組,以避免套用全域範圍時產生非預期的行為。

Where Not 子句

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

$products = DB::table('products')
->whereNot(function (Builder $query) {
$query->where('clearance', true)
->orWhere('price', '<', 10);
})
->get();

Where Any / All / None 子句

有時您可能需要將相同的查詢約束套用至多個欄位。例如,您可能想要檢索給定清單中任何欄位 LIKE 給定值的所有記錄。您可以使用 whereAny 方法來完成此操作。

$users = DB::table('users')
->where('active', true)
->whereAny([
'name',
'email',
'phone',
], 'like', 'Example%')
->get();

上面的查詢將產生以下 SQL

SELECT *
FROM users
WHERE active = true AND (
name LIKE 'Example%' OR
email LIKE 'Example%' OR
phone LIKE 'Example%'
)

同樣地,whereAll 方法可用於檢索所有給定欄位都符合給定約束的記錄。

$posts = DB::table('posts')
->where('published', true)
->whereAll([
'title',
'content',
], 'like', '%Laravel%')
->get();

上面的查詢將產生以下 SQL

SELECT *
FROM posts
WHERE published = true AND (
title LIKE '%Laravel%' AND
content LIKE '%Laravel%'
)

whereNone 方法可用於檢索沒有任何給定欄位符合給定約束的記錄。

$posts = DB::table('albums')
->where('published', true)
->whereNone([
'title',
'lyrics',
'tags',
], 'like', '%explicit%')
->get();

上面的查詢將產生以下 SQL

SELECT *
FROM albums
WHERE published = true AND NOT (
title LIKE '%explicit%' OR
lyrics LIKE '%explicit%' OR
tags LIKE '%explicit%'
)

JSON Where 子句

Laravel 也支援在提供 JSON 欄位類型支援的資料庫上查詢 JSON 欄位類型。目前,這包括 MariaDB 10.3+、MySQL 8.0+、PostgreSQL 12.0+、SQL Server 2017+ 和 SQLite 3.39.0+。若要查詢 JSON 欄位,請使用 -> 運算子。

$users = DB::table('users')
->where('preferences->dining->meal', 'salad')
->get();

您可以使用 whereJsonContains 來查詢 JSON 陣列。

$users = DB::table('users')
->whereJsonContains('options->languages', 'en')
->get();

如果您的應用程式使用 MariaDB、MySQL 或 PostgreSQL 資料庫,您可以將值陣列傳遞給 whereJsonContains 方法。

$users = DB::table('users')
->whereJsonContains('options->languages', ['en', 'de'])
->get();

您可以使用 whereJsonLength 方法依其長度查詢 JSON 陣列。

$users = DB::table('users')
->whereJsonLength('options->languages', 0)
->get();
 
$users = DB::table('users')
->whereJsonLength('options->languages', '>', 1)
->get();

其他 Where 子句

whereLike / orWhereLike / whereNotLike / orWhereNotLike

whereLike 方法允許您將 "LIKE" 子句新增到您的查詢以進行模式比對。這些方法提供了一種與資料庫無關的方式來執行字串比對查詢,並具有切換區分大小寫的能力。預設情況下,字串比對不區分大小寫。

$users = DB::table('users')
->whereLike('name', '%John%')
->get();

您可以使用 caseSensitive 參數啟用區分大小寫的搜尋。

$users = DB::table('users')
->whereLike('name', '%John%', caseSensitive: true)
->get();

orWhereLike 方法允許您新增具有 LIKE 條件的 "or" 子句。

$users = DB::table('users')
->where('votes', '>', 100)
->orWhereLike('name', '%John%')
->get();

whereNotLike 方法允許您將 "NOT LIKE" 子句新增到您的查詢中。

$users = DB::table('users')
->whereNotLike('name', '%John%')
->get();

同樣地,您可以使用 orWhereNotLike 來新增具有 NOT LIKE 條件的 "or" 子句。

$users = DB::table('users')
->where('votes', '>', 100)
->orWhereNotLike('name', '%John%')
->get();
exclamation

目前 SQL Server 不支援 whereLike 區分大小寫的搜尋選項。

whereIn / whereNotIn / orWhereIn / orWhereNotIn

whereIn 方法會驗證給定欄位的值是否包含在給定陣列中。

$users = DB::table('users')
->whereIn('id', [1, 2, 3])
->get();

whereNotIn 方法會驗證給定欄位的值是否未包含在給定陣列中。

$users = DB::table('users')
->whereNotIn('id', [1, 2, 3])
->get();

您也可以將查詢物件作為 whereIn 方法的第二個參數提供。

$activeUsers = DB::table('users')->select('id')->where('is_active', 1);
 
$users = DB::table('comments')
->whereIn('user_id', $activeUsers)
->get();

上面的範例將產生以下 SQL

select * from comments where user_id in (
select id
from users
where is_active = 1
)
exclamation

如果您要將大量整數繫結陣列新增到查詢中,則可以使用 whereIntegerInRawwhereIntegerNotInRaw 方法來大幅減少記憶體使用量。

whereBetween / orWhereBetween

whereBetween 方法會驗證欄位的值是否介於兩個值之間。

$users = DB::table('users')
->whereBetween('votes', [1, 100])
->get();

whereNotBetween / orWhereNotBetween

whereNotBetween 方法會驗證欄位的值是否位於兩個值之外。

$users = DB::table('users')
->whereNotBetween('votes', [1, 100])
->get();

whereBetweenColumns / whereNotBetweenColumns / orWhereBetweenColumns / orWhereNotBetweenColumns

whereBetweenColumns 方法會驗證欄位的值是否介於同一表格資料列中兩個欄位的兩個值之間。

$patients = DB::table('patients')
->whereBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight'])
->get();

whereNotBetweenColumns 方法會驗證欄位的值是否位於同一表格資料列中兩個欄位的兩個值之外。

$patients = DB::table('patients')
->whereNotBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight'])
->get();

whereNull / whereNotNull / orWhereNull / orWhereNotNull

whereNull 方法會驗證給定欄位的值是否為 NULL

$users = DB::table('users')
->whereNull('updated_at')
->get();

whereNotNull 方法會驗證欄位的值是否不為 NULL

$users = DB::table('users')
->whereNotNull('updated_at')
->get();

whereDate / whereMonth / whereDay / whereYear / whereTime

whereDate 方法可用於將欄位的值與日期進行比較。

$users = DB::table('users')
->whereDate('created_at', '2016-12-31')
->get();

whereMonth 方法可用於將欄位的值與特定月份進行比較。

$users = DB::table('users')
->whereMonth('created_at', '12')
->get();

whereDay 方法可用於將欄位的值與月份中的特定日期進行比較。

$users = DB::table('users')
->whereDay('created_at', '31')
->get();

whereYear 方法可用於將欄位的值與特定年份進行比較。

$users = DB::table('users')
->whereYear('created_at', '2016')
->get();

whereTime 方法可用於將欄位的值與特定時間進行比較。

$users = DB::table('users')
->whereTime('created_at', '=', '11:20:45')
->get();

whereColumn / orWhereColumn

whereColumn 方法可用於驗證兩個欄位是否相等。

$users = DB::table('users')
->whereColumn('first_name', 'last_name')
->get();

您也可以將比較運算子傳遞給 whereColumn 方法。

$users = DB::table('users')
->whereColumn('updated_at', '>', 'created_at')
->get();

您也可以將欄位比較陣列傳遞給 whereColumn 方法。這些條件將使用 and 運算子連接在一起。

$users = DB::table('users')
->whereColumn([
['first_name', '=', 'last_name'],
['updated_at', '>', 'created_at'],
])->get();

邏輯分組

有時您可能需要在括號內分組幾個 "where" 子句,以達到查詢所需的邏輯分組。事實上,為了避免非預期的查詢行為,您通常應該始終在括號內分組對 orWhere 方法的呼叫。若要完成此操作,您可以將閉包傳遞給 where 方法。

$users = DB::table('users')
->where('name', '=', 'John')
->where(function (Builder $query) {
$query->where('votes', '>', 100)
->orWhere('title', '=', 'Admin');
})
->get();

如您所見,將一個閉包傳遞給 where 方法會指示查詢建構器開始一個約束群組。該閉包會接收一個查詢建構器實例,您可以使用該實例來設定應包含在括號群組內的約束。上面的範例將產生以下 SQL

select * from users where name = 'John' and (votes > 100 or title = 'Admin')
exclamation

您應該始終將 orWhere 呼叫分組,以避免套用全域範圍時產生非預期的行為。

進階 Where 子句

Where Exists 子句

whereExists 方法允許您撰寫「where exists」SQL 子句。whereExists 方法接受一個閉包,該閉包會接收一個查詢建構器實例,讓您可以定義應放置在「exists」子句內的查詢。

$users = DB::table('users')
->whereExists(function (Builder $query) {
$query->select(DB::raw(1))
->from('orders')
->whereColumn('orders.user_id', 'users.id');
})
->get();

或者,您可以提供一個查詢物件給 whereExists 方法,而不是閉包。

$orders = DB::table('orders')
->select(DB::raw(1))
->whereColumn('orders.user_id', 'users.id');
 
$users = DB::table('users')
->whereExists($orders)
->get();

以上兩個範例都會產生以下 SQL

select * from users
where exists (
select 1
from orders
where orders.user_id = users.id
)

子查詢 Where 子句

有時您可能需要建構一個「where」子句,將子查詢的結果與給定的值進行比較。您可以透過將一個閉包和一個值傳遞給 where 方法來完成此操作。例如,以下查詢將檢索所有具有指定類型最近「會員資格」的使用者:

use App\Models\User;
use Illuminate\Database\Query\Builder;
 
$users = User::where(function (Builder $query) {
$query->select('type')
->from('membership')
->whereColumn('membership.user_id', 'users.id')
->orderByDesc('membership.start_date')
->limit(1);
}, 'Pro')->get();

或者,您可能需要建構一個「where」子句,將欄位與子查詢的結果進行比較。您可以透過將欄位、運算子和閉包傳遞給 where 方法來完成此操作。例如,以下查詢將檢索所有金額低於平均值的收入記錄:

use App\Models\Income;
use Illuminate\Database\Query\Builder;
 
$incomes = Income::where('amount', '<', function (Builder $query) {
$query->selectRaw('avg(i.amount)')->from('incomes as i');
})->get();

全文 Where 子句

exclamation

目前 MariaDB、MySQL 和 PostgreSQL 支援全文 where 子句。

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

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

排序、分組、限制和偏移

排序

orderBy 方法

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

$users = DB::table('users')
->orderBy('name', 'desc')
->get();

要依多個欄位排序,您可以根據需要多次調用 orderBy

$users = DB::table('users')
->orderBy('name', 'desc')
->orderBy('email', 'asc')
->get();

latestoldest 方法

latestoldest 方法可讓您輕鬆地按日期排序結果。預設情況下,結果將依資料表的 created_at 欄位排序。或者,您可以傳遞您希望排序依據的欄位名稱。

$user = DB::table('users')
->latest()
->first();

隨機排序

inRandomOrder 方法可用於隨機排序查詢結果。例如,您可以使用此方法來提取隨機使用者。

$randomUser = DB::table('users')
->inRandomOrder()
->first();

移除現有的排序

reorder 方法會移除先前已應用於查詢的所有「order by」子句。

$query = DB::table('users')->orderBy('name');
 
$unorderedUsers = $query->reorder()->get();

您可以在呼叫 reorder 方法時傳遞欄位和方向,以移除所有現有的「order by」子句,並將全新的排序應用於查詢。

$query = DB::table('users')->orderBy('name');
 
$usersOrderedByEmail = $query->reorder('email', 'desc')->get();

分組

groupByhaving 方法

正如您可能預期的那樣,可以使用 groupByhaving 方法來分組查詢結果。having 方法的簽名與 where 方法的簽名類似。

$users = DB::table('users')
->groupBy('account_id')
->having('account_id', '>', 100)
->get();

您可以使用 havingBetween 方法來篩選給定範圍內的結果。

$report = DB::table('orders')
->selectRaw('count(id) as number_of_orders, customer_id')
->groupBy('customer_id')
->havingBetween('number_of_orders', [5, 15])
->get();

您可以將多個參數傳遞給 groupBy 方法以依多個欄位分組。

$users = DB::table('users')
->groupBy('first_name', 'status')
->having('account_id', '>', 100)
->get();

若要建立更進階的 having 語句,請參閱 havingRaw 方法。

限制和偏移

skiptake 方法

您可以使用 skiptake 方法來限制從查詢傳回的結果數量,或跳過查詢中的給定數量結果。

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

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

$users = DB::table('users')
->offset(10)
->limit(5)
->get();

條件子句

有時,您可能希望某些查詢子句基於其他條件應用於查詢。例如,您可能只想在傳入的 HTTP 請求中存在給定的輸入值時才應用 where 語句。您可以使用 when 方法來完成此操作。

$role = $request->input('role');
 
$users = DB::table('users')
->when($role, function (Builder $query, string $role) {
$query->where('role_id', $role);
})
->get();

只有當第一個參數為 true 時,when 方法才會執行給定的閉包。如果第一個參數為 false,則不會執行閉包。因此,在上面的範例中,只有當傳入的請求中存在 role 欄位且其值為 true 時,才會調用提供給 when 方法的閉包。

您可以將另一個閉包作為第三個參數傳遞給 when 方法。只有當第一個參數的評估結果為 false 時,才會執行此閉包。為了說明如何使用此功能,我們將使用它來設定查詢的預設排序。

$sortByVotes = $request->boolean('sort_by_votes');
 
$users = DB::table('users')
->when($sortByVotes, function (Builder $query, bool $sortByVotes) {
$query->orderBy('votes');
}, function (Builder $query) {
$query->orderBy('name');
})
->get();

Insert 語句

查詢建構器還提供一個 insert 方法,可用於將記錄插入資料庫資料表。insert 方法接受欄位名稱和值的陣列。

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

您可以透過傳遞陣列的陣列來一次插入多個記錄。每個陣列代表應插入資料表的記錄。

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

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

DB::table('users')->insertOrIgnore([
['id' => 1, 'email' => '[email protected]'],
['id' => 2, 'email' => '[email protected]'],
]);

insertUsing 方法會將新記錄插入資料表,同時使用子查詢來判斷應插入的資料。

DB::table('pruned_users')->insertUsing([
'id', 'name', 'email', 'email_verified_at'
], DB::table('users')->select(
'id', 'name', 'email', 'email_verified_at'
)->where('updated_at', '<=', now()->subMonth()));

自動遞增 ID

如果資料表具有自動遞增的 ID,請使用 insertGetId 方法來插入記錄,然後檢索 ID。

$id = DB::table('users')->insertGetId(
['email' => '[email protected]', 'votes' => 0]
);
exclamation

使用 PostgreSQL 時,insertGetId 方法期望自動遞增的欄位命名為 id。如果您想從不同的「序列」檢索 ID,您可以將欄位名稱作為第二個參數傳遞給 insertGetId 方法。

Upserts

upsert 方法會插入不存在的記錄,並使用您可能指定的新值更新已存在的記錄。該方法的第一個參數包含要插入或更新的值,而第二個參數會列出唯一識別相關資料表內記錄的欄位。該方法的第三個也是最後一個參數是一個陣列,其中包含如果資料庫中已存在相符的記錄,則應更新的欄位。

DB::table('flights')->upsert(
[
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
],
['departure', 'destination'],
['price']
);

在上面的範例中,Laravel 將嘗試插入兩個記錄。如果已存在具有相同 departuredestination 欄位值的記錄,Laravel 將更新該記錄的 price 欄位。

exclamation

除了 SQL Server 之外的所有資料庫,都要求 upsert 方法的第二個參數中的欄位具有「主索引」或「唯一索引」。此外,MariaDB 和 MySQL 資料庫驅動程式會忽略 upsert 方法的第二個參數,並始終使用資料表的「主索引」和「唯一索引」來偵測現有記錄。

Update 語句

除了將記錄插入資料庫之外,查詢建構器還可以透過使用 update 方法來更新現有記錄。與 insert 方法類似,update 方法也接受欄位和值配對的陣列,指出要更新的欄位。update 方法會傳回受影響的列數。您可以使用 where 子句來約束 update 查詢。

$affected = DB::table('users')
->where('id', 1)
->update(['votes' => 1]);

更新或插入

有時您可能希望更新資料庫中的現有記錄,如果不存在相符的記錄則建立它。在這種情況下,可以使用 updateOrInsert 方法。updateOrInsert 方法接受兩個參數:用於查找記錄的條件陣列,以及指出要更新的欄位之欄位和值配對的陣列。

updateOrInsert 方法會嘗試使用第一個參數的欄位和值配對來尋找相符的資料庫記錄。如果記錄存在,則會使用第二個參數中的值進行更新。如果找不到記錄,則會插入一個新記錄,其中包含兩個參數的合併屬性。

DB::table('users')
->updateOrInsert(
['email' => '[email protected]', 'name' => 'John'],
['votes' => '2']
);

您可以為 updateOrInsert 方法提供一個閉包,以根據是否存在相符的記錄來自訂更新或插入資料庫的屬性。

DB::table('users')->updateOrInsert(
['user_id' => $user_id],
fn ($exists) => $exists ? [
'name' => $data['name'],
'email' => $data['email'],
] : [
'name' => $data['name'],
'email' => $data['email'],
'marketable' => true,
],
);

更新 JSON 資料行

更新 JSON 欄位時,您應該使用 -> 語法來更新 JSON 物件中的適當索引鍵。MariaDB 10.3+、MySQL 5.7+ 和 PostgreSQL 9.5+ 支援此操作。

$affected = DB::table('users')
->where('id', 1)
->update(['options->enabled' => true]);

遞增和遞減

查詢建構器還提供方便的方法來遞增或遞減給定欄位的值。這兩種方法都至少接受一個參數:要修改的欄位。可以提供第二個參數,以指定應遞增或遞減欄位的數量。

DB::table('users')->increment('votes');
 
DB::table('users')->increment('votes', 5);
 
DB::table('users')->decrement('votes');
 
DB::table('users')->decrement('votes', 5);

如果需要,您也可以在遞增或遞減操作期間指定其他要更新的欄位。

DB::table('users')->increment('votes', 1, ['name' => 'John']);

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

DB::table('users')->incrementEach([
'votes' => 5,
'balance' => 100,
]);

Delete 語句

查詢建構器的 delete 方法可用於從資料表刪除記錄。delete 方法會傳回受影響的列數。您可以在呼叫 delete 方法之前新增「where」子句來約束 delete 語句。

$deleted = DB::table('users')->delete();
 
$deleted = DB::table('users')->where('votes', '>', 100)->delete();

如果您希望截斷整個資料表,這將從資料表中移除所有記錄並將自動遞增的 ID 重設為零,則可以使用 truncate 方法。

DB::table('users')->truncate();

資料表截斷和 PostgreSQL

截斷 PostgreSQL 資料庫時,將應用 CASCADE 行為。這表示其他資料表中所有外來索引鍵的相關記錄也將被刪除。

悲觀鎖定

查詢建構器還包含一些函數,可協助您在執行 select 語句時實現「悲觀鎖定」。若要使用「共用鎖定」執行語句,您可以呼叫 sharedLock 方法。共用鎖定會阻止修改選定的列,直到您的交易提交為止。

DB::table('users')
->where('votes', '>', 100)
->sharedLock()
->get();

或者,您可以使用 lockForUpdate 方法。「for update」鎖定會阻止修改選定的記錄,或使用另一個共用鎖定來選取。

DB::table('users')
->where('votes', '>', 100)
->lockForUpdate()
->get();

除錯

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

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

可以在查詢上調用 dumpRawSqlddRawSql 方法,以轉儲查詢的 SQL,並正確替換所有參數繫結。

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