Getting the latest of relations in Laravel - php

I have this schema:
Ticket
----------
id, description
TicketStatus
-----------
id, name
Ticket_TicketStatus
-----------
id, ticket_id, status_id, latest (bool)
on Ticket model
public function statuses()
{
return $this->belongsToMany(TicketStatus::class, 'ticket_ticket_status', 'ticket_id', 'status_id');
}
public function getCurrentStatusAttribute()
{
return $this->statuses()->wherePivot('latest', 1)->first();
}
public function getStatusAttribute()
{
return $this->current_status->name;
}
public function scopeOpen($query)
{
return $query->whereHas('statuses', function (Builder $sub_query) {
$sub_query->where('latest', true)->where('is_open', true);
});
}
public function scopeClosed($query)
{
return $query->whereHas('statuses', function (Builder $sub_query) {
$sub_query->where('latest', true)->where('is_open', true);
});
}
What I'm trying to accomplish is to get all the tickets of a certain status that are the latest ones.
So I do something like this:
$not_mine_open_tickets = Ticket::open()
->orderBy('updated_at', 'desc')
->get();
But this is taking a lot of time to be executed on my database.
Anyone knows what's wrong?

But this is taking a lot of time to be executed on my database. Anyone knows what's wrong?
It could be many things, and it depends on a lot of factors.
What DB engine are you using?
How many rows are there?
What indexes are set up?
What is the current load of the database?
I'd start by looking at what the execution plan of your queries are and see what the estimated time to query is and what (if any) indexes are being used.
To get the query that would be executed, you can dump this out in your code:
dump(Ticket::open()
->orderBy('updated_at', 'desc')
->toSql());
dump(Ticket::open()
->orderBy('updated_at', 'desc')
->getBindings());
I'd then look to run this through some database software and look at the execution plan.
If you do some more research for your specific database engine you can find specific advice for next steps.

Related

laravel load candidates with given skills

In my website users can create candidates (one-many relation) and those candidates can have skills (many-many).
User model:
public function candidates() {
return $this->hasMany(Candidate::class);
}
candidate model:
public function skills() {
return $this->belongsToMany(Skill::class, 'candidate_skill', 'candidate_id', 'skill_id');
}
Skills model:
public function candidates() {
return $this->belongsToMany(Candidate::class, 'candidate_skill', 'candidate_id', 'skill_id')
->withTimestamps();
}
I already have an index page where an user can view ALL his made candidates
$candidates = Auth::user()->candidates;
on the edit page, skills can get synced to the candidate in question
$candidate->skills()->sync(request()->skills);
and back to the index page it shows how many skills the candidates have
<td>{{count($candidate->skills)}}</td>
now, I need to make a search bar. My table already has one(dataTable) to search in the already loaded td's. But I need a search bar to search for candidates which have certain skills, say I want to search for candidates which are synced with 'css' and only show those in the table.
I read through the laravel docs and tried eager loading it:
$hasCss = $candidates->load(['skills' => function ($query) {
$query->where('name', '=', 'css');
}]);
but this just loaded all candidates with only the css skill displayed, even the candidates who dont have it. I want to only load candidates who have the skill and leave the others out.
How do I do this, I'm clueless :s
You're looking for whereHas(...)
Current user's candidates that have CSS skill:
$withCss = Auth::user()->candidates()->whereHas('skills', function ($q) {
$q->where('name', 'css');
})->get();
In your Skill Model, you have an error in your relationship definition. You wrote
public function candidates() {
return $this->belongsToMany(Candidate::class, 'candidate_skill', 'candidate_id', 'skill_id')
->withTimestamps();
}
Swap 'candidate_id' and 'skill_id'. The local foreign key always comes first.
public function candidates() {
return $this->belongsToMany(Candidate::class, 'candidate_skill', 'skill_id', 'candidate_id')
->withTimestamps();
}

laravel pick last comment posted related to each post

I have got three tables in laravel like so:
Users, posts, and comments
I'm trying to come up with a query that fetches me all the user's posts, plus the date of last comment with each post.
Approach i've taken that's not working perfectly is:
$posts = User::find($userId)->posts()->with('latestComment')->get();
In my Post model I have:
public function latestComment()
{
return $this->hasOne(Comment::class)->latest();
}
In my findings, i haven't been to see a way to get the date from the lastComment load.
Any pointers welcome,
Thanks
Just discovered one needs to add the foreign key to the select method like so:
return $this->hasOne(Comment::class)->latest()->select('field','foreign_key');
You should use eager loading constraint. Code from the other answers will first load all comments, which you don't want.
$posts = Post::where('user_id', $userId)
->with(['comments' => function($q) {
$q->taletst()->take(1);
}])
->get();
You can use the existing relationship and get the latest comment.
public function comments() {
return $this->hasMany(Comment::class);
}
public function latestComment() {
return $this->comments()->last();
}

laravel eloquent sort by relationship

I have 3 models
User
Channel
Reply
model relations
user have belongsToMany('App\Channel');
channel have hasMany('App\Reply', 'channel_id', 'id')->oldest();
let's say i have 2 channels
- channel-1
- channel-2
channel-2 has latest replies than channel-1
now, i want to order the user's channel by its channel's current reply.
just like some chat application.
how can i order the user's channel just like this?
channel-2
channel-1
i already tried some codes. but nothing happen
// User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies'])
->orderBy('replies.created_at'); // error
}
// also
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies' => function($qry) {
$qry->latest();
}]);
}
// but i did not get the expected result
EDIT
also, i tried this. yes i did get the expected result but it would not load all channel if there's no reply.
public function channels()
{
return $this->belongsToMany('App\Channel')
->withPivot('is_approved')
->join('replies', 'replies.channel_id', '=', 'channels.id')
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'ASC');
}
EDIT:
According to my knowledge, eager load with method run 2nd query. That's why you can't achieve what you want with eager loading with method.
I think use join method in combination with relationship method is the solution. The following solution is fully tested and work well.
// In User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved');
}
public function sortedChannels($orderBy)
{
return $this->channels()
->join('replies', 'replies.channel_id', '=', 'channel.id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Then you can call $user->sortedChannels('desc') to get the list of channels order by replies created_at attribute.
For condition like channels (which may or may not have replies), just use leftJoin method.
public function sortedChannels($orderBy)
{
return $this->channels()
->leftJoin('replies', 'channel.id', '=', 'replies.channel_id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Edit:
If you want to add groupBy method to the query, you have to pay special attention to your orderBy clause. Because in Sql nature, Group By clause run first before Order By clause. See detail this problem at this stackoverflow question.
So if you add groupBy method, you have to use orderByRaw method and should be implemented like the following.
return $this->channels()
->leftJoin('replies', 'channels.id', '=', 'replies.channel_id')
->groupBy(['channels.id'])
->orderByRaw('max(replies.created_at) desc')
->get();
Inside your channel class you need to create this hasOne relation (you channel hasMany replies, but it hasOne latest reply):
public function latestReply()
{
return $this->hasOne(\App\Reply)->latest();
}
You can now get all channels ordered by latest reply like this:
Channel::with('latestReply')->get()->sortByDesc('latestReply.created_at');
To get all channels from the user ordered by latest reply you would need that method:
public function getChannelsOrderdByLatestReply()
{
return $this->channels()->with('latestReply')->get()->sortByDesc('latestReply.created_at');
}
where channels() is given by:
public function channels()
{
return $this->belongsToMany('App\Channel');
}
Firstly, you don't have to specify the name of the pivot table if you follow Laravel's naming convention so your code looks a bit cleaner:
public function channels()
{
return $this->belongsToMany('App\Channel') ...
Secondly, you'd have to call join explicitly to achieve the result in one query:
public function channels()
{
return $this->belongsToMany(Channel::class) // a bit more clean
->withPivot('is_approved')
->leftJoin('replies', 'replies.channel_id', '=', 'channels.id') // channels.id
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'desc');
}
If you have a hasOne() relationship, you can sort all the records by doing:
$results = Channel::with('reply')
->join('replies', 'channels.replay_id', '=', 'replies.id')
->orderBy('replies.created_at', 'desc')
->paginate(10);
This sorts all the channels records by the newest replies (assuming you have only one reply per channel.) This is not your case, but someone may be looking for something like this (as I was.)

Laravel Multiple Models Eloquent Relationships Setup?

I have 3 models
User
Pick
Schedule
I'm trying to do something like the following
$picksWhereGameStarted = User::find($user->id)
->picks()
->where('week', $currentWeek)
->first()
->schedule()
->where('gameTime', '<', Carbon::now())
->get();
This code only returns one array inside a collection. I want it to return more than 1 array if there is more than 1 result.
Can I substitute ->first() with something else that will allow me to to return more than 1 results.
If not how can I set up my models relationship to allow this to work.
My models are currently set up as follow.
User model
public function picks()
{
return $this->hasMany('App\Pick');
}
Schedule model
public function picks()
{
return $this->hasMany('App\Pick');
}
Pick model
public function user()
{
return $this->belongsTo('App\User');
}
public function schedule()
{
return $this->belongsTo('App\Schedule');
}
Since you already have a User model (you used it inside you find method as $user->id), you can just load its Pick relationship and load those Picks' Schedule as follows:
EDIT:
Assuming you have a schedules table and your picks table has a schedule_id column. Try this.
$user->load(['picks' => function ($q) use ($currentWeek) {
$q->join('schedules', 'picks.schedule_id', '=', 'schedules.id')
->where('schedules.gameTime', '<', Carbon::now()) // or Carbon::now()->format('Y-m-d'). See what works.
->where('picks.week', $currentWeek);
}])->load('picks.schedule');
EDIT: The code above should return the user's picks which have a schedules.gameTime < Carbon::now()
Try it and do a dump of the $user object to see the loaded relationships. That's the Eloquent way you want.
Tip: you may want to do $user->toArray() before you dump $user to see the data better.
EDIT:
The loaded picks will be in a form of Collections so you'll have to access it using a loop. Try the following:
foreach ($user->picks as $pick) {
echo $pick->schedule->gameTime;
}
If you only want the first pick from the user you can do: $user->picks->first()->schedule->gameTime
I think a foreach loop may be what you're looking for:
$picks = User::find($user->id)->picks()->where('week', $currentWeek);
foreach ($picks as $pick){
$pickWhereGameStarted = $pick->schedule()->where('gameTime', '<', Carbon::now())->get();
}
Try this and see if it's working for you

Limit result per category with Laravel 4.2 MySQL query

I've to build a research system for an announcements website. We can search through categories or via the announcement city.
When someone research something I want to set a limit of results per category (example : 3) using Eloquent.
I know this isn't easy at all, even in raw SQL, and it's been hours i'm trying to find a way without success. I tried to add a closure in the query but it's very hard for me to understand how to use that properly ...
I understood i needed to make a subquery to do the trick, but it's also tricky to know how to organize all this.
The workbench (followed by the current query which isn't working) :
Here's the controller with the query :
$announcements = Announcement::select('announcements.*')
->withCitySlug($location)
->withCategory($category_id)
->perCategoryLimit(3)
->get();
The model Announcement (the interesting part) :
/**
* HasManyThrough
*/
public function categories()
{
return $this->belongsToMany('Category', 'announcement_categories');
}
public function scopeWithCitySlug($query, $city_slug) {
if (empty($city_slug)) return $query;
return $query->where('city_slug', 'LIKE', '%'.$city_slug.'%');
}
public function scopeWithCategory($query, $category_id) {
if ($category_id === FALSE) return $query;
return $query->join('announcement_categories', 'announcement_categories.announcement_id', '=', 'announcements.id')
->join('categories', 'categories.id', '=', 'announcement_categories.category_id') ->where('categories.id', '=', $category_id);
}
public function scopePerCategoryLimit($query, $limit) {
return $query;
/*return $query->with(array('categories' => function($q)
{
$q->limit(3)->get();
}));*/
}
Any Eloquent/SQL expert around here ? A little help in the way to solve this would be perfect ;)
PS : If i didn't give enough code to understand the problem, just let me know !
Well, you can select all categories and use an foreach with a limit to get all results or use partition by from sql.

Categories