Trong bài này chúng ta cùng nhau tìm hiểu cách query với relationship và sự khác nhau giữ việc gọi method và gọi property
1. Gọi relationship
1.1. Gọi relationship như method
Khi chúng ta thực hiện gọi một relationship như một method trong model thì Laravel sẽ trả về một instance (query builder) chứa query của relationship đó mà chưa thực hiện một query đến database. Lúc này bạn có thể apply thêm các điều kiện khác vào relationship query đó
Ví dụ: Đối với relationship posts của User
app/Models/User.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get all of the posts for the user.
*/
public function posts()
{
return $this->hasMany(Post::class);
}
}
Lúc này mình có thể bổ sung điều kiện vào query relationship posts của user.
use App\Models\User;
$user = User::find(1);
// Lấy ra các post có active=1 của user đó.
$user->posts()->where('active', 1)->get();
Vấn đề với orWhere
Có 1 số trường hợp sử dụng orWhere có thể sẽ làm câu query của bạn bị sai
Ví dụ: Lấy tất cả các posts của user có active=1
hoặc votes>=100
use App\Models\User;
$user = User::find(1);
$user->posts()
->where('active', 1)
->orWhere('votes', '>=', 100)
->get();
Câu lệnh trên tương ứng với
select * from posts
where user_id = 1 and active = 1 or votes >= 100
Lúc này bạn có thể thấy điều kiện bắt buộc “user_id = 1” sẽ không đúng nữa, vì nó bị điều kiện or
kia ghi đè
Để fix lỗi này thì mọi người cần group điều kiện where kia vào
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
$user = User::find(1);
$user->posts()
->where(function (Builder $query) {
return $query->where('active', 1)
->orWhere('votes', '>=', 100);
})
->get();
Câu lệnh SQL sẽ như sau:
select * from posts where user_id = ? and (active = 1 or votes >= 100)
Điệu kiện “user_id = 1” vẫn sẽ là bắt buộc
1.2. Gọi relationship như property
Khi bạn thực hiện việc gọi relationship như một thuộc tính trong eloquent model thì laravel sẽ thực thi query dữ liệu đó với database cho bản ghi tương ứng. Bạn có thể hiểu khi chúng ta gọi relationship dưới dạng thuộc tính thì bằng với cách gọi relationship như một phương thức không apply thêm điều kiện gì
$user = User::find(1)->post;
// Tương đương với
$user = User::find(1)->post()->get();
2. Truy vấn với relationship
Khi bạn sử dụng relationship, bạn cũng có thể sử dụng kết quả của nó để filter data một cách tiện lợi với phương thức has
hoặc orHas
Ví dụ:
use App\Models\Post;
// Lấy ra các post có ít nhất một bình luận
$posts = Post::has('comments')->get();
// Lấy ra các post có ít nhất 3 comment
$posts = Post::has('comments', '>=', 3)->get();
Trong một số trường hợp bạn muốn thêm nhiều điều khiện khác vào trong câu lệnh của relationship, bạn có thể sử dụng whereHas
hoặc orWhereHas
use Illuminate\Database\Eloquent\Builder;
// Lấy ra post có it nhất một bình luận, content có từ "code*".
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
// Lấy ra post có it nhất 10 bình luận, content có từ code*.
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
}, '>=', 10)->get();
Đôi lúc bạn cần lấy ra các dữ liệu mà không tồn tại bản ghi nào matching với relationship, bạn có thể sử dụng phương thức doesntHave
hoặc orDoesntHave
.
Ví dụ: Lấy ra các posts không có comments
use App\Models\Post;
$posts = Post::doesntHave('comments')->get();
Cũng tương tự, bạn có thể sử dụng phương thức whereDoesntHave
hoặc orWhereDoesntHave
để thêm các điều kiện vào truy vấn relationship
Ví dụ: Lấy ra các post không có comment nào có nội dung chứa từ “code*“
use Illuminate\Database\Eloquent\Builder;
$posts = Post::whereDoesntHave('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
3. Aggregate Related Models
# withCount
Đếm số lượng bản ghi của relation mà không cần phải gọi đến model đó. Khi sử dụng hàm withCount
thì laravel sẽ thêm một attribute với tên có pattern “{relation}_count
” vào trong kết quả của model
Ví dụ: Lấy ra số lượng comment của post
use App\Models\Post;
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
Bạn cũng có thể sử dụng withCount
với nhiều relation một lúc bằng cách truyền vào một mảng relation
use Illuminate\Database\Eloquent\Builder;
$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
$query->where('content', 'like', 'code%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
Hoặc bạn cũng có thể sử dụng alisas cho relation count
Illuminate\Database\Eloquent\Builder;
$posts = Post::withCount([
'comments',
'comments as pending_comments_count' => function (Builder $query) {
$query->where('approved', false);
},
])->get();
echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;
Trong một số trường hợp model cha của bạn đã được load sẵn rồi, bạn có thể sử dụng phương thức loadCount
để lấy ra số lượng bản ghi của relation đó
$book = Book::first();
$book->loadCount('genres');
Tương tự bạn cũng có thể truyền thêm điều kiện vào trong relation query
$book->loadCount(['reviews' => function ($query) {
$query->where('rating', 5);
}]);
Nếu bạn sử dụng phương thức withCount
kết hợp với phương thức select
, bạn cần phải gọi phương thức select
trước khi gọi phương thức withCount
thì nó mới hoạt động
$posts = Post::select(['title', 'body'])
->withCount('comments')
->get();
# withMin, withMax, withAvg, withSum
Các phương thức withMin
, withMax
, withAvg
và withSum
dungf để lấy ra giá trị nhỏ nhất, lớn nhất, trung bình và tổng của dữ liệu trong relation với cú pháp:
withName($relation, $column);
Trong đó:
withName
là một trong các phương thứcwithMin
,withMax
,withAvg
hoặcwithSum
.$relation
là tên của relation các bạn muốn thực hiện query.$column
là column các bạn muốn query.
Khi thực hiện phương thức trên, Laravel sẽ thêm một attribute với pattern “{relation}_{function}_{column}
” chứa dữ liệu của query đó
Ví dụ: Lấy ra tổng số lượng votes của post
use App\Models\Post;
$posts = Post::withSum('comments', 'votes')->get();
foreach ($posts as $post) {
echo $post->comments_sum_votes;
}
Tương tự bạn cũng có thể sử dụng hàm load để lấy ra các giá trị của relation query khi bạn đã có sẵn model instance
post = Post::first();
$post->loadSum('comments', 'votes');
Leave a Comment