Adding parameters to the relation in laravel - php

I have a query the uses many ->with() in Laravel like so:
$campaign = $this->campaign
->with('tracks.flights.asset')->take(4)
->with('tracks.group')
->with('tracks.media')
->with('tracks.flights.comments')
->orderBy('created_at', 'desc')->find($id);
and in the Track model i have this method:
public function flights()
{
return $this->hasMany('Flight\Flight')->orderBy('start_date');
}
What I want to achieve is having the same result as now but i want to put date constrains on the flight level, for example something like this:
public function flights($start_date, $end_date) //dynamic dates
{
return $this->hasMany('Flight\Flight')->whereRaw('start_date > $start_date and end_date < $end_date')->orderBy('start_date');
}
How can I achieve this kind of result? I am fairly new to Laravel.
Thanks in advance.

Chain a whereHas onto your query.
$campaign = $this->campaign
->with('tracks.flights.asset')->take(4)
->with('tracks.group')
->with('tracks.media')
->with('tracks.flights.comments')
->whereHas('tracks.flights', function($q) use ($start_date, $end_date) {
$q->where('start_date', '>', $start_date)->where('end_date', '<', $end_date)->orderBy('start_date');
})
->orderBy('created_at', 'desc')->find($id);
Keep in mind the orderBy will only order the results returned within the flights relation, not the campaigns themselves.

This is the solution i came up with after reading Eloquent ORM in Laravel
$campaign = $this->campaign
->with(array('tracks.flights' => function($query)
{
$query->whereRaw('flights.start_date > "2016-01-09 00:00" AND flights.end_date < "2016-01-16 00:00"')
->with('asset')->take(4)
->with('comments');
}
))
->with('tracks.group')
->with('tracks.media')
->orderBy('created_at', 'desc')->find($id);

Related

Eloquent: has() condition against database value

I have a simple relationship between Bottles and InventoryItems:
InventoryItem.php:
public function bottles(): HasMany
{
return $this->hasMany(InventoryItemBottle::class);
}
I'm trying to query for InventoryItem's that have a bottles count of greater then a user entered threshold.
The user's input is saved in a JSONB. Part of the query looks like this and I've commented the problem line:
->when(
$filters['filter'] === 'show_above_max_threshold',
fn (Builder $query): Builder => $query->where(function (Builder $query): Builder {
return $query->whereColumn('info->quantity', '>', 'info->high_level_warning');
})
->orWhere(function (Builder $query): Builder {
return $query->has('bottles', '>', 'info->high_level_warning'); // stuck here
})
)
The has() method should help here, but how do I get the high_level_warning from the database to pass to it? Or is there another method I could use?
You can use the has function.
See if this helps you.
$threshold = 2;
$items = InventoryItem::has('bottles', '>=', $threshold)->get();

laravel eloquent complex select inside where statement

halo, i have data and want to display it like picture below
there are two models relationship, Person and Installment.
this is Person model:
class Person extends Model
{
protected $table = 'person';
public function angsuran()
{
return $this->hasMany(Installment::class);
}
}
this is Installment model:
class Installment extends Model
{
protected $table = 'installment';
public function person()
{
return $this->belongsTo(Person::class);
}
}
and this is my controller to querying and display data
$data = Person::with('angsuran')
->whereHas('angsuran', function ($q) {
$q->whereBetween('installment_date', [\DB::raw('CURDATE()'), \DB::raw('CURDATE() + INTERVAL 7 DAY')])
->where('installment_date', '=', function () use ($q) {
$q->select('installment_date')
->where('status', 'UNPAID')
->orderBy('installment_date', 'ASC')
->first();
});
});
return $data->get();
it show error unknow colum person.id in where clause
please help. thanks.
As the comment said, you need to put $q as a parameter to the Closure.
When using subqueries, it's useful to tell the query builder which table it is supposed to query from.
I've rewritten your query. It should achieve what you're looking for. Also, changed the CURDATE to Carbon objects.
today() returns a datetime to today at 00:00:00 hours. If you need the hours, minutes and seconds, replace today() by now().
$data = Person::with('angsuran')
->whereHas('angsuran', function ($subquery1) {
$subquery1->where('installment_date', function ($subquery2) {
$subquery2->from('installment')
->select('created_at')
->where('status', 'UNPAID')
->whereBetween('installment_date', [today(), today()->addWeeks(1)])
->orderBy('installment_date')
->limit(1);
});
});
Using with and whereHas you will end up with two query even if you have limit(1) in your subQuery and the result will show all 4 installment related to the person model. also I don't think you can order on the subquery, it should be before the ->get
so here's i've rewritten your code
$callback = function($query) {
$query->whereBetween('installment_date', [today(), today()->addDays(7)])
->where('status', 'UNPAID')
->orderBy('installment_date');
};
$data = Person::whereHas('angsuran', $callback)->with(['angsuran' => $callback])->get();
or you can use query scope. please see this answer Merge 'with' and 'whereHas' in Laravel 5

laravel filter only nested condition

I want to filter ONLY nested values in dealTransactions.
In plain English, Merchant wants only dealTransactions have provided dates with Deals.
I tried something like below but it does not work.
dates = ['2019-01-01', '2019-01-02', '2019-01-03', '2019-01-04', '2019-01-05'];
$merchant = Merchant::with(['deals.dealTransactions'])->where('slug', $slug)
->whereHas('deals.dealTransactions', function($query) use ($dates) {
foreach($dates as $date) {
$query->where('date', '=', $date);
}
})
->first();
deal_transactions table
id, deal_id, date
deals table
id, merchant_id,
merchants table
id, many columns for merchant
Thank you
You should be able to do this with a eager load constraint on the nested relationship:
$merchant = Merchant::where('slug', $slug)
->with(['deals.dealTransactions' => function ($query) use ($dates) {
$query->whereIn('date', $dates);
}])->first();
If I understood correctly your schema, this might help:
// Here you define the scope that will be used to select & eager load.
$transactionsScope = function ($q) use ($dates) {
return $q->whereIn('date', $dates);
};
// All merchant of slug in/with that transaction's scope.
$merchant = Merchant::where('slug', $slug)
->whereHas('deals', function ($q) use ($transactionsScope) {
return $q->whereHas('dealTransactions', $transactionsScope);
})
->with(['deals' => function ($q) use ($transactionsScope) {
return $q->with('dealTransactions', $transactionsScope);
}])
->firstOrFail();

How to compare related count with own column in Laravel Eloquent?

Assume we have an agents table with a quota column and a many-to-many relationship to tickets. With Laravel Eloquent ORM, how can I select only agents having less or equal number of 'tickets' than their 'quota'?
Eager-loading objects must be avoided.
class Agent extends Model {
public function tickets()
{
return $this->belongsToMany(Ticket::class, 'agent_tickets')->using(AgentTicket::class);
}
public function scopeQuotaReached($query)
{
// Does not work. withCount is an aggregate.
return $query->withCount('tickets')->where('tickets_count', '<=', 'quota');
// Does not work. Tries to compare against the string "quota".
return $query->has('tickets', '<=', 'quota');
}
}
Is there a more eloquent (pun intended) way to solve this than using a DB::raw() query with joining and grouping and counting manually?
EDIT
Works:
$query->withCount('tickets')->having('tickets_count', '<=', DB::raw('quota'))->get();
Works:
$query->withCount('tickets')->having('tickets_count', '<=', DB::raw('quota'))->exists();
Breaks: (throws)
$query->withCount('tickets')->having('tickets_count', '<=', DB::raw('quota'))->count();
RELATED
https://github.com/laravel/framework/issues/14492
Issue is closed, links to #9307, I have posted there. Will follow up.
Derived columns like tickets_count can only be accessed in the HAVING clause.
Since there is no havingColumn() method, you'll have to use a raw expression:
$query->withCount('tickets')->having('tickets_count', '<=', DB::raw('quota'));
At a database level I don't know how to achieve this, but you could do it at a Collection level.
// Get users
$agents = Agent::withCount('tickets')->get();
// filter
$good_agents = $agents->filter(function ($agent, $key) {
return $agent->tickets_count >= $agent->quota;
})
->all();
Of course you can inline it:
$good_agents = Agent
::withCount('tickets')
->get()
->filter(function ($agent, $key) {
return $agent->tickets_count >= $agent->quota;
})
->all();

Using Carbon for Laravel queries

how can I use Carbon to output created_at human readable queries like Facebook's 1 min ago on it's posts. The problem is I always return the results of the query to output in the browser directly. How can I integrate it so instead of 2015-07-01 12:32:43 it would be 1 min ago or other that's human readable and nice.
In my controller:
$posts = DB::table('posts')
->where('author_id', '=', $id)
->where('created_at', '<', $date)
->orderBy('created_at', 'desc')
->paginate(10);
return $posts;
It will then return a json format of all posts.
I recommend you use an Eloquent model and the features that come with it:
class Post extends Eloquent {
protected $appends = array('created_at_for_humans');
public function getCreatedAtForHumansAttribute(){
return Carbon::parse($this->attributes['created_at'])->diffForHumans();
}
}
This attribute accessor returns the formatted date and because it is registered in $appends the attribute will be included for array/JSON conversion.
And your query would look nearly the same:
$posts = Post::where('author_id', '=', $id)
->where('created_at', '<', $date)
->orderBy('created_at', 'desc')
->paginate(10);
Try:
foreach ($posts as &$post) {
$post->created_at = Carbon::parse($post->created_at)->diffForHumans();
}
before return

Categories