I have two models, beers and distributions, which have a many-to-many relationship. The pivot model hasMany kegs, which contain some relevant information to the beer such as pricing and status. When I build my beer index, I need all the information of the beer model, the distributor model, and the keg model. What I am trying to figure out is how to query for all the information in an efficient manner. Here is my current query:
Keg's are scoped on status:
public function scopeStatus($query, $status)
{
return $query->where('status', '=', $status);
}
and I build my beers index with:
$kegs = Keg::status($status)->get();
$beers=[];
foreach ($kegs as $keg){
$beer = Beer::find($keg->beer_distribution->beer_id);
$distributor = Distributor::find($keg->beer_distribution->distributor_id);
$beers[]=[
'beer' => $beer,
'keg' => $keg,
'distributor' => $distributor];
}
return $beers;
I know that this is a slow query but im not sure how to do this in a single query. Is there a way that I can run this faster?
Some relevant model code:
class Beer extends Eloquent {
public function distributors()
{
return $this->belongsToMany('Distributor', 'beer_distributions');
}
class BeerDistribution extends Eloquent {
protected $fillable = ['beer_id', 'distributor_id'];
public function kegs()
{
return $this->hasMany('Keg', 'beer_distribution_id');
}
class Distributor extends Eloquent {
public function beers()
{
return $this->belongsToMany('Beer', 'beer_distributions');
}
class Keg extends Eloquent {
public function scopeStatus($query, $status)
{
return $query->where('status', '=', $status);
}
public function beerDistribution()
{
return $this->belongsTo('BeerDistribution');
}
}
So I figured out that what I really needed to do was add my query building relations on my Keg model (which was the fatherest 'down' in the nest of relations), and then use eager loading!
I now build my beers index like so:
$beers=[];
foreach (Keg::status($status)
->with('kegsize',
'beerDistribution.beer.brewery',
'beerDistribution.beer.style',
'beerDistribution.distributor')->get() as $keg){
$beers[]=$keg;
}
return $beers;
This brings me down to a stunning total of 10 queries.
Related
I have this query and it runs in like 0.5s I need it to be faster is there a way to make this happen
$events_id = Event::where('user_id', Auth::user()->id)
->select("id")
->orderByDesc('id')->pluck('id');
$total_visitors_count = Visitor::select(DB::raw('count(*) as num_visits'))
->join('event_visitor', 'visitors.id', '=', 'event_visitor.visitor_id')
->whereIn('event_visitor.event_id', $events_id)
->count();
Relationships in laravel are a good way to keep our queries simple and fast, you can make relations between your entities to handle this:
User model class:
public function events()
{
return $this->hasMany(Event::class);
}
Event model class:
public function user()
{
return $this->belongsTo(User::class);
}
public function visitors()
{
return $this->belongsToMany(Visitor::class, 'event_visitor');
}
Visitor model class:
public function events()
{
return $this->belongsToMany(Event::class, 'event_visitor');
}
now, your queries become like this:
$events_id = auth()->user()->events()->orderByDesc('id')->pluck('id');
$total_visitors_count = Visitor::with('events')
->whereHas('events', fn ($q) => $q->whereIn('id', $events_id))
->count();
I have a requirement.
My DB has tables like the following.
The tables have OneToMany (1-n) parent-child relation.
Table School (id, school_name)
Table Class (id, school_id, class_name)
Table Section (id, class_id, section_name, no_of_seats)
Table Student (id, section_id, student_name, ....)
When Some Student is registered, data is uploaded to the Student table.
Now, I want to have a statistic like
| school_name | total_seats | student_registered |
and for a particular school
| class_name | total_seats | student_registered |
How to achieve this in Laravel/Eloquent
Thanks in Advance
Probably it works with:
Counting/Summarizing HasMany relations
Counting/Summarizing HasManyThrough relations
Counting/Summarizing HasManyDeep relations
Definition
class Section extends Model
{
public function students(): HasMany
{
return $this->hasMany(Student::class);
}
public function scopeWithRegisteredStudents(Builder $query): Builder
{
// Count HasMany relation
return $query->withCount('students as students_registered');
}
}
// The word "Class" is reserved, so we need to use "SchoolClass" instead
class SchoolClass extends Model
{
protected $table = 'classes';
public function sections(): HasMany
{
return $this->hasMany(Section::class, 'class_id');
}
public function students(): HasManyThrough
{
return $this->hasManyThrough(Student::class, Section::class, 'class_id');
}
public function scopeWithTotalSeats(Builder $query): Builder
{
// Summarize field from HasMany relation
return $query->withSum('sections as total_seats', 'no_of_seat');
}
public function scopeWithRegisteredStudents(Builder $query): Builder
{
// Count HasManyThrough relation
return $query->withCount('students as students_registered');
}
}
class School extends Model
{
public function classes(): HasMany
{
return $this->hasMany(SchoolClass::class);
}
public function sections(): HasMany
{
return $this->hasManyThrough(Section::class, SchoolClass::class, null, 'class_id');
}
public function students(): HasManyThrough
{
// https://github.com/staudenmeir/eloquent-has-many-deep
return $this->hasManyDeep(Student::class, [SchoolClass::class, Section::class], ['school_id', 'class_id', 'section_id'], ['id', 'id', 'id']);
}
public function scopeWithTotalSeats(Builder $query): Builder
{
// Summarize field from HasManyThrough relation
return $query->withSum('sections as total_seats', 'no_of_seat');
}
public function scopeWithRegisteredStudents(Builder $query): Builder
{
// Count HasManyDeep relation
return $query->withCount('students as students_registered');
}
}
Example
// Fetching simply
Section::query()
->withRegisteredStudents()
->get();
SchoolClass::query()
->withTotalSeats()
->withRegisteredStudents()
->get();
School::query()
->withTotalSeats()
->withRegisteredStudents()
->get();
// Fetching with nested relations
School::query()
->withTotalSeats()
->withRegisteredStudents()
->with(['classes' => function (HasMany $query) {
return $query
->withTotalSeats()
->withRegisteredStudents();
}])
->get();
If you use a static analyzer like PHPStan or Psalm, you can alternatively use scopes method to prevent errors.
School::query()
->scopes(['withTotalSeats', 'withRegisteredStudents'])
->get();
This is not what you asked for as it uses Query Builder instead of Eloquent. I have not tested it as I have nothing to test against currently but this should work -
use Illuminate\Support\Facades\DB;
$students_per_section = DB:table('students')
->select('section_id', DB::raw('COUNT(id) AS num_students'))
->groupBy('section_id')
$query = DB:table('schools')
->join('classes', 'schools'.'id', '=', 'classes.school_id')
->join('sections', 'classes.id', '=', 'sections.class_id')
->leftJoinSub($students_per_section, 'students_per_section', function($join) {
$join->on('sections.id', '=', 'students_per_section.section_id')
});
if ($school_id) {
$query
->select('classes.class_name', DB::raw('SUM(no_of_seats) AS total_seats'), DB::raw('SUM(students_per_section.num_students) AS student_registered'))
->where('schools.id', '=', $school_id)
->groupBy('classes.class_name')
} else {
$query
->select('schools.school_name', DB::raw('SUM(no_of_seats) AS total_seats'), DB::raw('SUM(students_per_section.num_students) AS student_registered'))
->groupBy('schools.school_name')
}
$stats = $query->get();
I have these relationship between school and associate models:
// School model
public function associates()
{
return $this->belongsToMany('Associate', 'school_associate', 'school_id', 'associate_id')
->withPivot('start_date', 'end_date');
}
// Associate model
public function schools()
{
return $this->belongsToMany('School', 'school_associate', 'associate_id', 'school_id')
->withPivot('start_date', 'end_date');
}
I need to get all associates of one school ordered by start_date.
This is what I tried without success (in this try I am searching in all schools):
dd(\App\Associate::with(['schools' => function ($q) {
$q->orderBy('pivot_start_date', 'desc');
}])->toSql());
And I get this sql (notice no order by clause):
select * from `associate`
I tried to edit the relationship like this:
// Associate model
public function schools()
{
return $this->belongsToMany('School', 'school_associate', 'associate_id', 'school_id')
->withPivot('start_date', 'end_date')
->orderBy('pivot_start_date', 'desc'); // also tried without "pivot_"
}
And according to this post, I also tried :
// Associate model
public function schools()
{
return $this->belongsToMany('School', 'school_associate', 'associate_id', 'school_id')
->withPivot('start_date', 'end_date')
->orderBy('school_associate.start_date', 'desc');
}
But I always get the same query and the results are not ordered.
I solved using query builder in this way.
This function is in Associate model:
public function scopeLast($query, $school_ids = [])
{
$query->join('school_associate', "{$this->table}.{$this->primaryKey}", '=', 'school_associate.associate_id')
->join('school', 'school.school_id', '=', 'school_associate.school_id')
->whereIn('school.school_id', $school_ids)
->orderBy('school_associate.start_date', 'desc');
return $query;
}
I want to get users filtered by category, so I send category_id as a parameter and should select users who haven't records in users_categories_restrictions table with this category_id.
How can I make this eloquent query ??
You could use whereDoesntHave
$users = App\User::whereDoesntHave('categories', function ($query) use($cat_id) {
$query->where('categories.id', '=', $cat_id);
})->get();
I assume you have defined many to many relation between user and category model
class User extends Model
{
public function categories()
{
return $this->belongsToMany(Category::class, 'users_categories_restrictions', 'user_id');
}
}
class Category extends Model
{
public function users()
{
return $this->belongsToMany(User::class, 'users_categories_restrictions', 'category_id');
}
}
I am submitting a POST request to my API endpoint using Postman.
In my nested JSON, an artist has one or more albums and each album has one or more songs.
I have two questions:
1) How do I perform nested array validation in Laravel? I am looking for an optimal / standard Laravel way to do so.
2) How do I save multiple models together?
Note: I did create the relationships in my Eloquent models, such as
class Artist extends Eloquent {
public function albums()
{
return $this->hasMany('Album');
}
}
class Album extends Eloquent {
public function songs()
{
return $this->hasMany('Song');
}
}
class Song extends Eloquent {
public function album()
{
return $this->belongsTo('Album');
}
}
class Album extends Eloquent {
public function artist()
{
return $this->belongsTo('Artist');
}
}
1) Use the validator's each method:
$validator = Validator::make(Input::all(), [...rules...]);
$validator->each('albums', [...rules...]);
2) After creating the artist, loop through your albums and call create on the relationship:
$artist = Artist::create(Input::all());
foreach (Input::get('albums') as $album)
{
$artist->albums()->create($album);
}