Laravel: Use order by in combination with whereHas - laravel

I want to retrieve a collection of data, which is ordered by the start_date of the relation
Basically I want to achieve this, with Laravel Models (the code below works perfectly)
$posts = DB::table('posts')
->leftJoin(
'threads',
'posts.id',
'=',
'threads.postable_id'
)
->where('threads.postable_type', '=', 'App\Post')
->orderBy('threads.start_date')
->paginate($request->input('limit', 2));
So in this case, I'm fetching ALL Posts and those are ordered by the start_date of the thread relation.
Those are not my actual tables but this works perfectly!
Because I'm using https://laravel.com/docs/8.x/eloquent-resources this is not the ideal solution to retrieve sorted data.
So instead I want to use the orderBy clause somewhere here
$posts = Post::whereHas('thread', function ($query) {
$query->where('end_date', '>=', Carbon::now());
});
But I just cannot make this work. I've tried this
$posts = Post::whereHas('thread', function ($query) {
$query->where('end_date', '>=', Carbon::now())
->orderBy('start_date');
});
and I also appended this to the actual relation:
public function thread(): MorphOne
{
return $this->morphOne('App\Thread', 'postable')->orderBy('start_date');
}

If you look at your code:
$posts = Post::whereHas('thread', function ($query) {
$query->where('end_date', '>=', Carbon::now())
->orderBy('start_date');
});
the whereHas will only return Post associate with a thread which the function return true.
Try this:
$posts = Post::with('thread')->has('thread')->orderBy('thread.start_date')->get();
This will fetch all Post with Thread only if they have at least one Thread and then orderBy the start_date of the Thread.
You don't have to do the whereHas function because when you call ->with('thread') it'll use you this :
public function thread(): MorphOne
{
return $this->morphOne('App\Thread', 'postable')->orderBy('start_date');
}
whereHas doesnt retrieve the relationship.
If you need even more power, you may use the whereHas and orWhereHas methods to define additional query constraints on your has queries, such as inspecting the content of a comment: Laravel whereHas
Don't do :
$posts = Post::with('thread')->orderBy('thread.start_date');
If there is no thread on some post, post without thread will be fetch with value null for their key thread and you will have an unexpected result when you try to orderBy.

First of all I want to thank Elie Morin for his help but I found out that I definitely need to use joins for that task.
In my example, I wanted to order the main query (posts) by the relation's start_date
Doing what you suggested
$posts = Post::with('thread')->has('thread')->orderBy('thread.start_date')->get();
Would only order the thread by start_date and not the ENTIRE query.
Which is why I came up with something like this:
$posts = Post::has('thread')
->select('posts.id')
->leftJoin(
'thread',
'posts.id',
'=',
'thread.postable_id'
)
->where('thread.postable_type', '=', 'App\Post')
->where('thread.end_date', '>=', Carbon::now())
->orderBy('thread.start_date')
->with('thread');
return PostResource::collection($posts->paginate(2));

Related

Add Parameter to Laravel Eloquent relationship in "with" function

I would like to add a parameter to my Eloquent relationship in the with:: static method.
I have a Dealer Model that has those relationships
public function events()
{
return $this->hasMany('App\Event', 'dealer_id', 'dealer_id');
}
public function recentEvents($interval = 14)
{
return $this->events()
->whereDate('datestart', '>=', Carbon::now('America/Toronto')->subDays($interval))
->whereDate('dateend', '>=', Carbon::now('America/Toronto'));
}
Everything works fine if I call it like this $dealer->recentEvents(20) but in my DealerController
I would like to do something in the line of this
Dealer::with("recentEvents")
->where('dealer_flag', 1)
->orderBy('dealer_storename', 'ASC')
->get();
Where i would pass a paremeter to "recentEvents".
I've tried putting a closure function to with like this
Dealer::with(["recentEvents" => function ($query) use ($interval){
//The problem here is that I would need to put the recent-events code like this
$query->whereDate('cal_datestart', '>=', Carbon::now('America/Toronto')->subDays($interval))
->whereDate('cal_dateend', '>=', Carbon::now('America/Toronto'));
}])
->where('dealer_flag', 1)
->orderBy('dealer_storename', 'ASC')
->get();
But it would defeat the purpose of having a recentEvents relationship.
Is it possible to just pass a parameter ?
I've tried using Dynamic Scope from Laravel doc but I figured it was not what I was looking for.
i think u should use like this
Dealer::with(["events",function($q) use($interval){
$q->whereDate('datestart', '>=', Carbon::now('America/Toronto')->subDays($interval))
->whereDate('dateend', '>=', Carbon::now('America/Toronto'));
}])
->where('dealer_flag', 1)
->orderBy('dealer_storename', 'ASC')
->get();

use of orWhere() in Eloquent

I have a eloquent query that I am running twice, but feel can be run once.
I'd want to return the values of the first where statement if exists otherwise check the second where statement which is the default in the query.
This is what I am currently doing:
$details = Telco::select('telcos.id AS telco_id', 'telcos.name AS telco_name')
->leftJoin('telco_prefixs', 'telco_prefixs.telco_id', '=', 'telcos.id')
->where('telco_prefixs.prefix', '=', $phone_number) // check if ndc exists
->first();
if ($details){
return $details;
}
return Telco::select('telcos.id AS telco_id', 'telcos.name AS telco_name')
->leftJoin('telco_prefixs', 'telco_prefixs.telco_id', '=', 'telcos.id')
->where('telcos.name', '=', 'Default') //default channel
->first();
I have a feeling this can be combined to something like below:
However, this fails as keeps executing the OrWhere clause.
Telco::select('telcos.id AS telco_id', 'telcos.name AS telco_name')
->leftJoin('telco_prefixs', 'telco_prefixs.telco_id', '=', 'telcos.id')
->where('telco_prefixs.prefix', '=', $phone_number) // if ndc exists
->Orwhere('telcos.name', '=', 'Default') //default channel
->first();
Someone help. Thanks
Try to add your where and or where condition as below.
Telco::select('telcos.id AS telco_id', 'telcos.name AS telco_name')
->leftJoin('telco_prefixs', 'telco_prefixs.telco_id', '=', 'telcos.id')
->where(function ($query) use($phone_number) {
$query->where('telco_prefixs.prefix', '=', $phone_number);
$query->Orwhere('telcos.name', '=', 'Default');
})->first();
I you want to use just eloquent a possible solution would be to have something like:
I suppose you have model called Telco and another one calle TelcoPrefix.
//Telco.php
//first we create a has many relationship with your telco_prefixs table.
public function telcoPrefixs(){
return $this->hasMany(TelcoPrefix::class);
}
Once you have that relationship you can use something similar to this code:
Telco::whereHas('telcoPrefixs',function($query, $phone_number){
return $query->where('prefix,'=',$phone_number);
})->select('id','name')->first();
This will compare the relationship and if it exists or has it will return your first record in one query.
Hope it helps at least to give a guide of what you can do.

Join Laravel tables

I have got three tables Kudos, Kudoscategories, and specialpost. I need to get the data from those tables and check the post id using a where condition.
I have attached the database table screenshot here
I already tried this, but it's not getting any result.
$results = DB::table('kudos')
->select('kudos.description','kudoscategory.catname','kudoscategory.image','kudos.spid')
->join('kudoscategory', 'kudoscategory.id', '=', 'kudos.categoryid')
->where('kudos.spid', '=', $post_id)
->first();
return $results;
What I need to do is get the results using below where condition newsfeed_special_posts.main_post_id = kudos.spid
You must add another join with newsfeed_special_posts.
The query will be like,
$results = DB::table('kudos')
->select('kudos.description','kudoscategory.catname','kudoscategory.image','kudos.spid')
->join('kudoscategory', 'kudoscategory.id', '=', 'kudos.categoryid')
->join('newsfeed_special_posts', 'newsfeed_special_posts.main_post_id', '=', 'kudos.spid')
->where('kudos.spid', '=', $post_id)
->first();
return $results;
Though this is not a good practice for laravel. using laravel eloquent relationships will be more efficient for these results.
https://laravel.com/docs/5.8/eloquent-relationships
you can still do it this way. still efficient to avoid sql injection since you are getting all the record from the 3 tables. seeing your table above, its not properly normalised. anywaye try this.
$results = DB::table('kudos')
->leftjoin('kudoscategory', 'kudoscategory.id', '=', 'kudos.categoryid')
->leftjoin('newsfeed_special_posts', 'newsfeed_special_posts.id', '=', 'kudos.spid')
->where('kudos.spid', '=', $post_id)
->first();
return $results;
you can try this code
at first, create model Kudos,KudosCategory,NewsfeedSpecialPosts
Kudos Model
public function kudoscategory_dta()
{
return $this->belongsTo('App\Model\KudosCategory','categoryid');
}
public function newsfeed_special_posts_dta()
{
return $this->belongsTo('App\Model\NewsfeedSpecialPosts','spid','main_post_id ');
}
controller
Kudos::with('kudoscategory_dta','newsfeed_special_posts_dta')->first();

Best way to return the sum aggregation of a relations property

I have a simple structure where a post has many votes. A vote has an "value" property which is either 1 or -1
When reading all posts I'd love to select this sum for each post into a custom property on post level. Currently i do this
$posts = Post::where('published_at', '<=', $date)
->orderBy('published_at', 'desc')
->simplePaginate(20);
$posts->each(function($post) {
$post->overallRating = $post->getRating();
});
This is fully working, however I think it's not that good to make like 20 queries to the database to read the ratings. Is there a way to simplify this in the actual fetch of the posts?
public function getRating()
{
return $this->votes->sum('value');
}
If you want to keep the votes included in the in the pagination results then I would suggest adding with('votes') so they're at least eager loaded i.e.
$posts = Post::with('votes')
->where('published_at', '<=', $date)
->orderBy('published_at', 'desc')
->simplePaginate(20);
However, if you don't want/aren't bothered about having the votes and you just want the ratings for each post you could add the following scope to your Post model:
public function scopeWithRating(Builder $query)
{
if (is_null($query->getQuery()->columns)) {
$query->select([$query->getQuery()->from . '.*']);
}
$query->selectSub(
$this->votes()->getRelationExistenceQuery(
$this->votes()->getRelated()->newQuery(), $query, new Expression('sum(value)')
)->toBase(),
'rating'
);
}
Then:
$posts = Post::withRating()
->where('published_at', '<=', $date)
->orderBy('published_at', 'desc')
->simplePaginate(20);
Hope this helps!
Try this:
$posts = Post::where('published_at', '<=', $date)
->orderBy('published_at', 'desc')
->with(['votes' => function($query) {
$query->sum('value');
}])->simplePaginate(20);

How to query a table using result of many-to-many relation with Eloquent ORM?

here is link to my database schema:
How can I get all topics from the blogs to which the user is subscribed using his id? I use Eloquent ORM.
I have added the following lines to my User model:
public function blogs()
{
return $this->belongsToMany('Blog', 'blog_subscriptions');
}
And then requested in the controller
User::find(1)->blogs; // User id is hardcoded
With that I got all blogs to which the user is subscribed.
And now I am stuck on how to get the topics of those blogs.
Assuming relations:
// User
belongsToMany('Blog', 'user_subscriptions')
// Blog
belongsToMany('User', 'user_subscriptions')
hasMany('Topic')
// Topic
belongsTo('Blog')
1 My way, that will work with any relation, no matter how deeply nested:
User::with(['blogs.topics' => function ($q) use (&$topics) {
$topics = $q->get()->unique();
}])->find($userId);
2 Eloquent standard methods:
$topics = Topic::whereHas('blog', function ($q) use ($userId) {
$q->whereHas('users', function ($q) use ($userId) {
$q->where('users.id', $userId);
});
})->get();
// or
$user = User::find($userId);
foreach ($user->blogs as $blog)
{
$blog->topics; // collection of topics for every single blog
}
3 Joins:
$topics = Topic::join('blogs', 'topics.blog_id', '=', 'blogs.id')
->join('user_subscriptions as us', function ($j) use ($userId) {
$j->on('us.blog_id', '=', 'blogs.id')
->where('us.user_id', '=', $userId);
})->get(['topics.*']);
Mind that last solution will rely on your pivot data consistency. That said, if you can't be sure, that there are no redundant entries in user_subscriptions (eg. blog or user has been deleted but the pivot entry remains), then you need to further join users as well.
I am new to laravel and I don't have the possibility of test this, but I had a similar need and solved it with something like this:
DB::Query('topics')->join('blogs', 'blogs.id', '=', 'topics.blog_id')
->join('blog_subscriptions', 'blogs.id', '=', 'blog_subscriptions.blog_id')
->select('name.id','table.email')
->where('blog_subscriptions.user_id', '=', $user_id);

Resources