Laravel get only first related model - php

I want to get the first related model. But this works only for the first model in the collection. The 2nd is empty.
I've found this answer, but I didn't find a solution.
How can I only get the first related model?
$querybuilder->with([
'messages' => function ($query) {
$query->orderBy("created_at", "DESC");
$query->limit(1);
}
]);

You can use a HasOne relationship:
class Conversation extends Model {
public function latestMessage() {
return $this->hasOne(Message::class)->latest();
}
}
$querybuilder->with('latestMessage');
Be aware that this will still fetch all messages from the database. It then discards the "old" ones.
If you want to improve the performance by really only fetching the latest message, you can use this package I created: https://github.com/staudenmeir/eloquent-eager-limit
The package allows you to apply limit() to the relationship:
class Conversation extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
public function latestMessage() {
return $this->hasOne(Message::class)->latest()->limit(1);
}
}

Your with() actually creates several queries, the last query has the limit, hence the behavior (which is correct). you can use \DB::enableQueryLog();, run your query and then \DB::getQueryLog(); to see how the queries are built.
If you instead want to apply a limit to each model item you could fetch all items and iterate over them to fetch one or more related model items
This is not done i sql but in php (laravel collection method), if you need it in sql you could just join your related model and set it up however you want.
This will cause performance issues if you have large amount of data, but if you don't it's quite convenient.
$result = \App\YourModel::all()
->map(function ($item) {
return $item->YourRelatedModel()
->orderBy('someField')
->first();
});
I forgot ... the above only returns the related model's items, if you also want the parent model you can
$result = \App\YourModel::all()
->map(function ($item) {
$item->YourRelatedModelName = $item
->YourRelatedModel()
->orderBy('someField')
->first();
return $item;
});

Related

Get model which has all specific related models

I have no idea how to solve my problem.
I have Model1 with relation:
class Model1 extends BaseModel
{
public function details(): BelongsToMany
{
return $this->belongsToMany(
Detail::class,
'model1_details',
'model1_id',
'detail_id',
);
}
}
model1_details is my pivot table.
Also I have array of details ids like [1, 2, 3].
I need to fetch Model1 that belongs to ALL Detail with given ids.
I need to do this inside my filter.
That's what I have inside my controller:
$builder = Model1::filter(new ModelListFilter($request));
and inside ModelListFilter:
protected function filter(): Builder
{
$request = $this->request;
$request->whenFilled('details', function ($query) {
//
});
return $this->builder;
}
I've tried:
$request->whenFilled('details', function ($query) {
foreach($query as $detailId) {
$this->builder->whereHas('details', function (Builder $innerQuery) use ($detailId) {
$innerQuery->where('detail_id', $detailId);
});
}
});
But it returns all models Model1 even without any details.
UPD
So the problem wasn't there =) 'details' just wasn't filled in my Request.
also my $query was a string, not array, so I called json_decode on it.
Code above retrieves Models belonging to detail with id=1 AND to detail with id=2 and so on.
But I think there might be better solution so I'll leave this question open
UPD 2
also changed this
$innerQuery->where('detail_id', $detailId);
to this
$innerQuery->where('id', $detailId);
so here is needed to pass columns we have in 'details' table, not columns from pivot table
I can't see where you load the relationship with details. Not sure if you're missing a file, but in case you're not, load the relationship in the first part of the query with the eager loading with method:
$builder = Model1::with('details')->filter(new ModelListFilter($request));
You are almost there but you still missing two things,
WhereIn list
to get model whereHas their details ids in a list you need to use WhereIn instead of looping the query
Eager load the details relationship
to get the details list with each model you have to use the with keyword to eager load the relationship
Solution
// builder
$builder = Model1::with('details')->filter(new ModelListFilter($request));
// filter
$request->whenFilled('details', function (array $details) {
$this->builder->whereHas('details', function (Builder
$innerQuery) use ($details) {
$innerQuery->whereIn('detail_id', $details);
}
});
Found solution to my problem here. So it seems like there is no better solution(

Laravel 5.8 - How sort (orderBy) multiple relationship

I am trying to sort the serials by video views.
Relations:
The Serial has a hasMany relationship to series.
The Series has a hasMany relationship to episodes.
The Episodes has a hasOne relationship to video.
The Video has a hasMany relationship to viewcounts.
<?php
//sort method:
public function mostPopular()
{
$serials = Serial::with(['series.episodes.video' => function ($query) {
$query->withCount(['videoViews' => function($query) {
}])->orderBy('video_views_count', 'desc');
}])->get();
return $serials;
}
//Serial model:
public function series()
{
return $this->hasMany(Series::class);
}
//Series model:
public function episodes()
{
return $this->hasMany(Episode::class);
}
public function serial()
{
return $this->belongsTo(Serial::class);
}
//Episode model:
public function video()
{
return $this->hasOne(Video::class);
}
public function series()
{
return $this->belongsTo(Series::class);
}
//Video model:
public function videoViews()
{
return $this->hasMany(VideoView::class);
}
public function episode()
{
return $this->belongsTo(Episode::class);
}
?>
I expect the sorted serials by video views (series.episodes.video.videoViews), but the actual output is not sorted.
Laravel 5.8
PHP 7
This is a silly one actually but I've learnt that multiple ->sortBy on collections actually are possible with no workarounds. It's just that you need to reverse the order of them. So, to sort a catalogue of artists with their album titles this would be the solution...
Instead of :
$collection->sortBy('artist')->sortBy('title');
Do this :
$collection->sortBy('title')->sortBy('artist');
Because "With" queries run as seperate queries (not subqueries as previously suggested), exposing extrapolated fuax-columns from one query to the other gets rather tricky. I'm sure there's non-documented solution in the API docs but I've never come across it. You could try putting your with and withCount in the orderBy:
Serial::orderBy(function($query) { some combo of with and withCount })
But that too will get tricky. Since either approach will hit the database multiple times, it would be just as performant to do the separation yourself and keep your sanity at the same time. This first query uses a left join, raw group by and raw select because I don't want laravel running the with query as a separate query (the problem in the first place).
$seriesWithViewCounts = VideoView::leftJoin('episodes', 'episodes.id', '=', 'video_views.episode_id')
->groupBy(DB::raw('episodes.series_id'))
->selectRaw("episodes.series_id, count(video_views.id) as views")
->get();
$series = Series::findMany($seriesWithViewCounts->pluck('series_id'));
foreach($series as $s) {
$s->view_count = $seriesWithViewCounts->first(function($value, $key) use ($s) {
return $value->series_id = $s->id
})->video_views_count;
});
$sortedSeries = $series->sortBy('video_views_count');
This will ignore any series that has no views for all episodes, so you may want to grab those and append it to the end. Not my definition of "popular".
I'd love to see a more eloquent way of handling this, but this would do the job.

Copying Data from table to another table and applying one to many relationship

This is my Report Model
protected $fillable = [
'site_url',
'reciepients',
'monthly_email_date'
];
public function site()
{
return $this->belongsTo('App\Site');
}
This is my Site Model
public function report()
{
return $this->hasMany('App\Report');
}
This is my ReportController
public function showSpecificSite($site_name)
{
$records = DB::table('reports')
->select('email_date','url','recipient')
->whereHas('sites', function($query){
$query->where('site_name',$site_name);
})
->get();
return view('newsite')->with('records',$records)
->with('site_name',$site_name);
}
My Controller is not yet working as well.
The thing is I would like to copy all the three files from sites table to reports table.
Is it possible in insertInto ?
My code on ReportController shows you that I'm selecting data from reports table but I am the one who puts data to reports table to see the output but it is not yet working because of the it cant reach out the value of site_name even though I already put a relationship between the two tables.
You're not actually using Eloquent in your controller you're just using the Query Builder (DB). This will mean that you don't have access to anything from your Eloquent models.
Try:
$records = \App\Report::whereHas('site', function($query) use($site_name) {
$query->where('site_name', $site_name);
})->get(['id', 'email_date', 'url', 'recipient']);
I've added id to the list of columns as I'm pretty sure you'll need that to use whereHas.
NB to use a variable from the parent scope inside a closure you need to pass it in using use().

Laravel orWhere not working with hasMany

I have following code:
class Ingredient extends Eloquent
{
public function units()
{
return $this->hasMany('IngredientUnit')
->orWhere('ingredient_id', '=', -1);
}
}
I would expect query like:
select * from `ingredient_units` where `ingredient_id` = '-1' OR `ingredient_units`.`ingredient_id` in (...)
instead I get:
select * from `ingredient_units` where `ingredient_id` = '-1' and `ingredient_units`.`ingredient_id` in (...)
Why it use AND operator instead OR, when I used orWhere()?
Update 1:
And second question is how can I get a query which I was expected?
Update 2:
I want to use eagerloading for that
When you fetch a collection of objects through a relation on a model, the relation constraint is always included, hence the AND. And it makes perfect sense, otherwise you could get $model->units objects that are not related to $model.
I can see what you're trying to achieve here - fetch units related to that $model together with units not related to any models. You can achieve it by adding the following method to your model:
public function getAllUnits() {
$genericUnits = IngredientUnit::whereIngredientId(-1);
return $this->units()->union($genericUnits)->get();
}
OR
public function getAllUnits() {
return IngredientUnit::whereIn('ingredient_id', [$this->id, -1])->get();
}
The only issue here is that it won't be used by eager loading logic but would result in separate query for every model for which you want to return units. But if you always fetch that for a single model anyway, it won't be a problem.
One suggestion: store NULL in ingredient_id instead of -1. This way you'll be able to make use of foreign key constraints.

Laravel 5 hasMany relationship on two columns

Is it possible to have a hasMany relationship on two columns?
My table has two columns, user_id and related_user_id.
I want my relation to match either of the columns.
In my model I have
public function userRelations()
{
return $this->hasMany('App\UserRelation');
}
Which runs the query: select * from user_relations where user_relations.user_id in ('17', '18').
The query I need to run is:
select * from user_relations where user_relations.user_id = 17 OR user_relations.related_user_id = 17
EDIT:
I'm using eager loading and I think this will affect how it will have to work.
$cause = Cause::with('donations.user.userRelations')->where('active', '=', 1)->first();
I don't think it's possible to do exactly what you are asking.
I think you should treat them as separate relationships and then create a new method on the model to retrieve a collection of both.
public function userRelations() {
return $this->hasMany('App\UserRelation');
}
public function relatedUserRelations() {
return $this->hasMany('App\UserRelation', 'related_user_id');
}
public function allUserRelations() {
return $this->userRelations->merge($this->relatedUserRelations);
}
This way you still get the benefit of eager loading and relationship caching on the model.
$cause = Cause::with('donations.user.userRelations',
'donations.user.relatedUserRelations')
->where('active', 1)->first();
$userRelations = $cause->donations[0]->user->allUserRelations();
Compoships adds support for multi-columns relationships in Laravel 5's Eloquent.
It allows you to specify relationships using the following syntax:
public function b()
{
return $this->hasMany('B', ['key1', 'key2'], ['key1', 'key2']);
}
where both columns have to match.
I'd prefer doing it this way:
public function userRelations()
{
return UserRelation::where(function($q) {
/**
* #var Builder $q
*/
$q->where('user_id',$this->id)
->orWhere('related_user_id',$this->id);
});
}
public function getUserRelationsAttribute()
{
return $this->userRelations()->get();
}
If anyone landed here like me due to google:
As neither merge() (as suggested above) nor push() (as suggested here) allow eager loading (and other nice relation features), the discussion is still ongoing and was continued in a more recent thread, see here: Laravel Eloquent Inner Join on Self Referencing Table
I proposed a solution there, any further ideas and contributions welcome.
You can handle that things with this smart and easy way .
$cause = Cause::with(['userRelations' => function($q) use($related_user_id) {
$q->where('related_user_id', $related_user_id);
}])->where('active', '=', 1)->first();

Categories