Apply where condition only to one occurrence - php

I'm building search functionality for my app. To simplify things:
there are two tables: shops and subscriptions
Each shop can have multiple subscription records, subscription has field expires_at. Now, I assume that shop has active subscription if subscription exsists and at least one of shop's subscripion expires_at date is bigger than now().
It is one of the conditions to the whole query. Here is code:
$shops = Shop::when($subscription, function($query, $subscription) {
$query->doesntHave('subscriptions')->orWhereHas('subscriptions', function($q) use ($subscription, $query) {
$query->where('expires_at', '<', now());
});
});
It doesn't work as expected because if i.e. shop has three related subscriptions and at least one of them is expired – it assumes that shop has no active subscription (even though it has).
I would need to implement some nested function inside or whereHas, I guess, to sort by expires_at desc and then limit to one and only then pass where expires_at clause, however I've no idea how.
And I rather need to stick with Eloquent Query Builder rather than DB facade or raw sql.
Basically, it is the same problem what wasn't answered here:
https://laracasts.com/discuss/channels/eloquent/latest-record-from-relationship-in-wherehas?page=1

Try this:
$shops = Shop::doesntHave('subscriptions')->orWhereHas('subscriptions', function ($query) {
$query->whereDate('expires_at', '<', now());
})->get();

try this :
$shops = Shop::WhereHas('subscriptions')->withCount('subscriptions as
active_subscriptions_count' => function ($query) {
$query->where('expires_at', '<', now());
}])->having('active_subscriptions_count', '>=', 3)->get();

Ok, so after some tries in raw sql I figured it out:
$query->select('shops.*')
->leftJoin('subscriptions', 'shops.id', 'subscriptions.shop_id')
->whereNull('subscriptions.id')
->orWhere('subscriptions.expires_at', '<', now())
->where('subscriptions.id', function($q) {
$q->select('id')
->from('subscriptions')
->whereColumn('shop_id', 'shops.id')
->latest('expires_at')
->limit(1);
});
It is not only faster than where exists clause but also gives me what I needed – only the "highest" subscription for given shop is under consideration.

Related

How to find if there is a pivot table used in an Eloquent scope method?

Currently I have 3 tables
users
id, name
discount
id, name, active
discount_user
discount_id, user_id, start, end
I need to find all the active discounts for a current user which are currently running based on the start and the
end dates held on the pivot table.
I was hoping to be able to build a scopeActive method on the discount table to narrow down active discounts for the
current user, but I only want the date range to be added if I'm coming from the user table to find the discounts:
$discounts = User::find(1)->discounts()->active()->get(); // within date range
$active = Discount::active()->get(); // without date range check
On the user table I've extended the relations to have an 'activeDiscounts' relation which works using:
public function activeDiscounts() {
return $this->discounts()
->where('active', true)
->wherePivot('start', '>=', Carbon::now())
->wherePivot('end', '<=', Carbon::now());
}
Though this works, I don't think it's really best practice and would prefer to be able to use a scope on the discount.
Is there any way I can tell if I'm coming from the user table or through a pivot on the scope query? Also, if so, would I be
able to see the User ID so I can use it in the pivot query?
To answer on your question: You could use arguments in scope, e.g:
$discounts = User::find(1)->discounts()->active(true)->get(); // within date range
$active = Discount::active()->get();
Scope:
public function scopeActive($query, $withDates = false) {
return $query
->where('active', true)
->when($withDates, function($query){
$query
->wherePivot('start', '>=', Carbon::now())
->wherePivot('end', '<=', Carbon::now());
});
}
But I think that it is not better than your solution.
Also, you could use 2 different scopes.

Filter elements by counting their relationship Laravel5

I have two tables, the typical master-detail, what I intend to do is obtain the master's records, while the registration number obtained by means of a where of the relationship is greater than n
I've been doing this until now
Master::withTrashed()
->with('details')
->withCount(['details' => function ($query) {
$query->whereDate('date_init', '<', Carbon::now()->toDateString());
}]);
with this I get the master and its details with the given conditions, but there are teachers that the count of the relationship is 0, that I want to ignore. How can I do this?
What you are looking for is the following query:
Master::withTrashed()
->with(['details' => function ($query) {
$query->whereDate('date_init', '<', now());
}])
->whereHas('details', function ($query) {
$query->whereDate('date_init', '<', now());
})
->get();
The query literally means:
Give me all Masters, also the deleted ones, which have at least one attached Detail with an initialization date in the past. Also eager load all attached Details which have been initialized in the past.
Master::withTrashed()
->has('details')
->with('details')
->withCount(['details' => function ($query) {
$query->whereDate('date_init', '<', Carbon::now()->toDateString());
}]);
Fixed whereHas to has, as pointed out by Namoshek.

Laravel Eloquent - building 'where not' query with relationship

I have 5 database rows with the same client_id, 3 labelled completed, Yes.
This code pulls through 3 results as expected:
$indGoal = $client->indGoal()->where('completed','=','Yes')->get();
This code pulls through no results: I would expect 2.
$indGoal = $client->indGoal()->where('completed','!=','Yes')->get();
This question suggests adding ->orWhereNull('completed') - which works, but ignores the client_id relationship. The request brings through all non-Yes results, regardless of $client
My Client model for reference:
public function indGoal()
{
return $this->hasMany('App\Models\IndGoal');
}
You should group orWhere filters in a callback so they don't interfere with existing filters.
$indGoal = $client->indGoal()
->where(function ($query) {
$query->orWhere('completed', '!=', 'yes')
->orWhereNull('completed');
})
->get();
This way, the query builder knows any of the grouped conditions should be true and all other conditions are independent.

Fetching a summary grouped by month with Eloquent

I have a table called gk and I am currently running two queries. Please have a look at the queries:
Gk::groupBy(DB::raw("MONTH(created_at)"))
->groupBy(DB::raw("YEAR(created_at)"))
->selectRaw('id, user_id, sum(ton) as ton,pl, count(id) as total, sum(w) , created_at')
->with(array('user'=> function($q){
$q->select('id', 'userName', 'profilePic');
}))
->where('user_id', $userData[0]->id)
->get();
This query returns a little summary of every months. As you can I see I am grouping results by months and years. And I have another query which will return all the rows of any given months.
I am running second query like this
$m=Carbon::parse($request->date);
Gk::where('user_id',$request->user_id)->whereRaw(DB::raw("YEAR(created_at)=$m->year"))->whereRaw(DB::raw("MONTH(created_at)=$m->month"))
->orderBy('created_at','desc')
->get();
The second query returns all the rows of any month. I'm executing this query in a foreach loop for all of the months that are returned in the first query.
I am trying to combine this two query into one so that I can get a group of the results by months and years and also all the details of that month.
Any help, suggestions or idea would be extremely helpful.
[Note: For the date in second query, this date is created_at result from the first query.]
Thank you.
The way I read your question is as following: The second query is executed in a loop with results from the first one. Is that right? In my answer I have explained a way to execute the second query just one time instead of in a loop. You'd still have to execute the first query once.
So, I think that you are better of using the Php collection methods:
$results = Gk::where('user_id',$request->user_id)
->orderBy('created_at','desc')
->get()
->groupBy(function (Gk $item) {
return $item->created_at->format('Y-m');
});
The groupBy method has to return an attribute on which you want to group the elements. For this example I think that using a yyyy-mm format will do fine.
Reference: https://laravel.com/docs/5.5/collections#method-groupby
Edit: Maybe you can also get rid of the orderBy method call because you are grouping by afterwards:
$results = Gk::where('user_id',$request->user_id)
->get()
->groupBy(function (Gk $item) {
return $item->created_at->format('Y-m');
});
Edit 2: To combine the information of the two queries, you could do something like the following:
$results = Gk::where('user_id',$request->user_id)
->get()
->groupBy(function (Gk $item) {
return $item->created_at->format('Y-m');
})->map(function(Collection $rows) {
return [
'sum(ton)' => $rows->sum('ton'),
'total' => $rows->count(),
'sum(w)' => $rows->sum('w'),
'rows' => $rows
];
);
Note that I have omitted a few of the selected columns in your first query because they are not unique in the given group by. But feel free to add any logic in the map function. Because we use map() after groupBy, every call of map() will receive a collection of items for one month. You can that use that fabulous collection magic to calculate all values that you need and reduce the number of queries to just one.

Select all records where a relationship count is zero using Eloquent?

In plain English: I have three tables. subscription_type which has many email_subscriptions which has many emails.
I'm trying to select all email_subscription records that have a particular subscription_type, that also don't have any associated email records that have a status of Held.
The particular bit I am stuck on is only returning email_subscriptions which have zero emails (with an additional where clause stacked in there described above).
Using Eloquent, I've been able to get a bit of the way, but I don't have any idea how to select all the records that have a relationship count of zero:
$subscriptionsWithNoCorrespondingHeldEmail = EmailSubscriptions::whereHas('subscriptionType', function($q) {
$q->where('name', 'New Mission');
})-; // What do I chain here to complete my query?
Additionally, is this even possible with Eloquent or will I need to use Fluent syntax instead?
You can use the has() method to query the relationship existence:
has('emails', '=', 0)
Eg:
$tooLong = EmailSubscriptions::whereHas('subscriptionType', function($q) {
$q->where('name', 'New Mission');
})->has('emails', '=', 0)->get();
Edit
You can do more advanced queries with the whereHas() and whereDoesntHave() methods:
$tooLong = EmailSubscriptions::whereHas('subscriptionType', function($q) {
$q->where('name', 'New Mission');
})
->whereDoesntHave('emails', function ($query) {
$query->where('status', '=', 'whatever');
})->get();
OK what I have under stand from your Question is you would like to have a All Emails which have
specific subscription_type, Zero(0) association and status = status
If yes so you canuse array in where statement.
Like:
$q->->where(array('status' => 'status','subscription_type'=>'what ever you want));

Categories