Eloquent Performance Pattern - notes
use laravel debugbar
composer require barryvdh/laravel-debugbar --dev
use index to make it faster
// everything in column field needs to be indexed
order by {field}, where {field} = 1
do not use *
// *
User::query()
	->with('author')
	->latest('published_at')
	->get()
	->groupBy(fn($post) => $post->published_at->year);
use select
User::query()
	->select('id','title','slug','published_at','author_id') // <- this
	->with('author')
	->latest('published_at')
	->get()
	->groupBy(fn($post) => $post->published_at->year);
within with() can select too
User::query()
	->select('id','title','slug','published_at','author_id')
	->with('author:id,name') // <- this
	->latest('published_at')
	->get()
	->groupBy(fn($post) => $post->published_at->year);
When using with() loads more data, subquery only can return 1 column back
$user = User::query()
	->addSelect(['last_login_at' => 
		Login::select('created_at')
			->whereColumn('user_id', 'users.id')
			->latest()
			->take(1)
	]) // instead of with
	->withCasts(['last_login_at' => 'datetime']) // instead of diffForHumans()
	->orderBy('name')
	->paginate();
dynamic relationship
$user = User::query()
	->withLastLogin()
	->orderBy('name')
	->paginate();
public function lastLogin() {
	return $this->belongsTo(Login::class);
}
public function scopeWithLastLogin($query) {
	$query->addSelect(['last_login_id' => Login::select('id')
		->whereColumn('user_id', 'users.id')
		->latest()
		->take(1)
	])->with('lastLogin');
}
conditional aggregates
select
	count(case when status = 'Requested' then 1 end) as requested,
	count(case when status = 'Planned' then 1 end) as planned,
	count(case when status = 'Completed' then 1 end) as completed
from features
$statuses = Feature::toBase()
	->selectRaw("count(case when status = 'Requested' then 1 end) as requested")
	->selectRaw("count(case when status = 'Planned' then 1 end) as planned")
	->selectRaw("count(case when status = 'Completed' then 1 end) as completed")
	->first();
optimizing circular relationship
public function show(Feature $feature)
{
	$feature->load('comments.user');
	$feature->comments->each->setRelation('feature',$feature);
	return view('feature', ['feature' => $feature]);
}
multi column searching
$users = User::query()
	->search(request('search'))
	->with('company')
	->paginate();
return view('users', ['users' => $users]);
public function scopeSearch($query, string $terms = null) {
	collect(explode(' ', $terms))->filter()->each(function($term) use ($query) {
		$term = '%' . $term . '%';
		$query->where(function($query) use ($term) {
			$query->where('first_name', 'like', $term)
				->orWhere('last_name', 'like', $term)
				->orWhereHas('company', function($query) use ($term) {
					$query->where('name', 'like', $term);
				});
		}
	}
}
normalize field using regex
Schema::created('companies', function(Blueprint $table) {
	...
	$table->string('first_name_normalize')
	->virtualAs("regexp_replace(first_name, '[^A-Za-z0-9]', '')")->index();
	...
});
limit rows by user_id with loggedin user
public function scopeVisibleTo($query, User $user) {
	if ($user->is_owner) {
		return;
	}
	$query->where('sales_rep_id', $user->id);
}
compound index where it’s a multiple index into 1, the order of the index matters, the example needs to be last_name first then the first_name not the other way around.
$table->index(['last_name','first_name']);
subquery has one relationship, order without using leftjoin
$user = User::query()
	->orderBy(Company::select('name')
		->whereColumn('user_id','users.id')
		->orderBy('name')
		->take(1)
	)
	->with('company')
	->paginate();
subquery belongs to relationship,
order by without using leftjoin but for this case leftjoin is faster so use left join.
//join (faster)
$user = User::query()
	->select('users,*')
	->join('companies','companies.id','=','users.company_id')
	->orderBy('companies.name')
	->with('company')
	->paginate();
//subquery
$user = User::query()
	->orderBy(Company::select('name')
		->whereColumn('id','users.company_id')
		->orderBy('name')
		->first()
	)
	->with('company')
	->paginate();
subquery has many relationship
$user = User::query()
	->select('users.*')
	->join('logins', 'logins.user_id', '=', 'users.id')
	->orderByDesc('logins.created_at')
	->groupBy('users.id') // error below
	->withLastLogin()
	->paginate();
trouble when adding groupBy → order by clause is not in group by clause and contains nonaggregated column
// use this
->orderByRaw('max(logins.created_at) desc')
subquery
$user = User::query()
	->select('users.*')
	->orderByDesc(Login::select('created_at')
		->whereColumn('user_id', 'users.id')
		->latest()
		->take(1)
	)
	->withLastLogin()
	->paginate();
Subquery belongs to many
$books = Book::query()
	->select('books.*')
	->join('checkouts', 'checkouts.book_id', '=', 'books.id')
	->groupBy('books.id')
	->orderByRaw('max(checkouts.borrowed_date) desc')
	->withLastCheckout()
	->with('lastCheckout.user')
	->paginate();
// to subquery
$books = Book::query()
	->orderByDesc(Checkout::select('borrowed_date')
		->whereColumn('book_id', 'books.id')
		->latest('borrowed_date')
		->take(1)
	)
	->withLastCheckout()
	->with('lastCheckout.user')
	->paginate();
// another way
$books = Book::query()
	->orderByDesc(function($query) {
			$query->select('borrowed_date')
				->from('checkouts)
				->whereColumn('book_id', 'books.id')
				->latest('borrowed_date')
				->take(1)
		}
	)
	->withLastCheckout()
	->with('lastCheckout.user')
	->paginate();
// another way
$books = Book::query()
	orderBy(User::select('name')
		->join('checkouts', 'checkouts.book_id', '=', 'books.id')
		->whereColumn('checkouts.book_id', 'books.id')
		->latest(checkouts.borrowed_date')
		->take(1)
	)
	->withLastCheckout()
	->with('lastCheckout.user')
	->paginate();
order with nulls first
$book = Book::query()
	->with('user')
	->orderByDesc('user_id') // messes up the order name
	->orderBy('name')
	->paginate();
$book = Book::query()
	->with('user')
	->orderByRaw('user_id is null')
	->orderBy('name')
	->paginate();
// this query shows the null first
$users = User::query()
	->when(request('sort') === 'town', function($query) {
			$query->orderBy('town', request('direction'));
		})
	->orderBy('name')
	->paginate();
$users = User::query()
	->when(request('sort') === 'town', function($query) {
			$query->orderByRaw('town is null')
				->orderBy('town', request('direction'));
		})
	->orderBy('name')
	->paginate();
Ordering by custom algorithm
status using when
public function scopeOrderByStatus($query, $direction)
{
	$query->orderBy(DB::raw('
		case
			when status = "Requested" then 1
			when status = "Approved" then 2
			when status = "Completed" then 3
		end
	'), $direction);
}
then in migration
Schema::create('features', function(Blueprint $table) {
	$table->rawIndex('(
		case
			when status = "Requested" then 1
			when status = "Approved" then 2
			when status = "Completed" then 3
		end
	'), 'feature_status_ranking_index');
			
}
activity using count
$features = Feature::query()
	->withCount('comments', 'votes')
	...
public function scopeOrderByActivity($query, $direction)
{
	$query->orderBy(
		DB::raw('(votes_count + (comments_count *))'),
		$direction
	)
}
Filtering and sorting
$users = User::query()
	->orderByBirthday()
	->whereBirthdayThisWeek()
public function scopeOrderByBirthday($query)
{
	$query->orderByRaw('date_format(birth_date, "%m-%d")');
}
Schema::create('features', function(Blueprint $table) {
	$table->rawIndex("date_format(birth_date, '%m-%d'), name", 'users_birthday_name_index');
}
public function scopeWhereBirthdayThisWeek($query)
{
	$query->whereRaw('date_format(birth_date, "%m-%d") between ? and ?', [
		Carbon::now()->startOfWeek()->format('m-d'),
		Carbon::now()->endOfWeek()->format('m-d'),
	]);
	// OR
	$query->whereRaw(date_format(birthdate, '%m-%d') in (?,?,?,?,?,?) ,
		iterator_to_array($dates));
}
Fulltext
DB::statement('
	create fulltext index posts_fulltext_index
	on posts(title, body)
	with parser ngram
');
// example
select id, title, match(title, body) against ('fox' in boolean mode) as score
from posts
where match(title, body) against ('fox' in boolean mode)
$posts = Post::query()
	->with('author')
	->when(request('search'),function($query, $search) {
		$query->selectRaw('*,match(title, body) against(? in boolean mode)' as score, [$search])
			->whereRaw('match(title, body) against(? in boolean mode)', [$search])
	}, function($query) {
		$query->latest('published_at');
	})
	->paginate();
Getting distance between geo points
Schema::create('stores', function(Blueprint $table) {
	...
	$table->point('location', 4326);
	...
});
$myLocation = [-79.47, 43.14];
$stores = Store::query()
	->selectDistanceTo($myLocation)
	->withinDistanceTo($myLocation, 10000)
	->orderByDistance($myLocation)
	->paginate();
public function scopeWithinDistanceTo($query, [] $coordinates, int $distance) {
	$distance = strtolower($direction) === 'asc' ? 'asc', : 'desc';
	$query->whereRaw('ST_Distance(
		location,
		ST_SRID(Point(?, ?),4326),
	) <= ?', $direction, $coordinates);
});
public function scopeWithinDistanceTo($query, [] $coordinates, int $distance) {
	$query->whereRaw('ST_Distance(
		ST_SRID(Point(longitude, latitude),4326),
		ST_SRID(Point(?, ?),4326),
	) <= ?', [...$coordinates, $distance]);
});
public function scopeSelectDistanceTo($query, array $coordinates) {
	if (is_null($query->getQuery()->columns)) {
		$query->select('*');
	}
	$query->selectRaw('ST_Distance(
		ST_SRID(Point(longitude, latitude),4326),
		ST_SRID(Point(?, ?),4326),
	) as distance', $coordinates);
});
// change to this
	$query->selectRaw('ST_Distance(
		location,
		ST_SRID(Point(?, ?),4326),
	) as distance', $coordinates);
filter by geospatial
Schema::create('customer', function(Blueprint $table) {
	...
	$table->point('location');
	...
});
Schema::create('regions', function(Blueprint $table) {
	...
	$table->geometry('geometry');
	...
});
public function scopeInRegion($query, $region) {
	$query->whereRaw('ST_Contains(?, customers.location)', [$region->geometry]);
});