Laravel Eloquent 'whereBetween' doesn't work - laravel-5

I am trying to get a list in a given time period as below:
public function UsersWork()
{
return $this->hasMany('App\Models\UsersWork', 'user_id')->whereBetween('end', ["DATE_SUB(CURDATE() INTERVAL 5 YEAR)", date('Y-m-d')]);
}
This is returning the whole list and not just the expected list for time period of 5 years.
Can you pinpoint the reasons for this behavior ?

You have to use DB::raw() when you want to use raw sql functions.
public function UsersWork()
{
return $this->hasMany('App\Models\UsersWork', 'user_id')
->where(function($q){
$q->where(DB::raw('recur_at BETWEEN DATE_SUB(NOW(), INTERVAL 7 DAY) AND NOW()'));
});
}
It may be necessary to add use DB; on top before the class declaration.

Related

Eloquent - apply value from relationship to where in scope

So I have this need to check if a customer needs to be called. Customers has to be called at intervals depending on a value days_between_calls in a BelongsTo model called SubscriberType. I got it to work but I don't like it, maybe there is a cleaner way.
So I have a model Subscription with relations :
public function subscriberType()
{
return $this->belongsTo(SubscriberType::class);
}
public function calls()
{
return $this->hasMany(Call::class);
}
and a (very simplified) scope :
public function scopeNeedsCall(Builder $query) {
$query->join('subscriber_types', 'subscriber_types.id', '=', 'subscriptions.subscriber_type_id')
->whereDoesntHave('calls', function(Builder $query) {
$query->whereRaw('calls.created_at > DATE_SUB(NOW(), INTERVAL days_between_calls DAY)');
});
}
Is there any cleaner way to use this days_between_calls field's value without manually joining its table and without writing raw sql?
Thanks ahead.
So it looks like there is not much that can be improved, and I do need a rawsql part here. I improved it a little anyway using https://laravel.com/docs/9.x/eloquent-relationships#has-one-of-many but that's about it.

Add some records to all eloquent query results

I have a table named food_portion like the following:
id|food_id|name|gram_weight
1|102030|slice|183
2|102030|pie|183
3|102031|waffle|35
....
The table is complete, But some global portions are missing like gram/oz..
I wanted to write a query to add records for this portions but I'm thinking that its not a good choice because this portions have same value for all the foods.
*|*|gr|1 (6000 records like this)
*|*|oz|28 (and another 6000 like this)
So I'm looking for a way to modify my model (food_portion) so every time I execute some query using model get the the above records without having them physically in the database table, So my queries wouldn't be slow for no reason.
How can I do this. I tried to do this using global scope but I failed:
protected static function booted()
{
static::addGlobalScope('global_portions', function (Builder $builder) {
$builder->orWhere( function($query)
{
//$query->where("food_id","*")->where("name","gr") ???
// what should I write here?
});
});
}
Bottom line is I want to prevent record repetition for every food.
I want to add two specific records to every query result.
Thanks in advance
I think you are very close, check this:
use Illuminate\Support\Facades\DB;
protected static function booted()
{
static::addGlobalScope('global_portions', function (\Illuminate\Database\Eloquent\Builder $builder) {
$builder->union(DB::query()->select([
DB::raw("\"*\" AS id"),
DB::raw("\"*\" AS food_id"),
DB::raw("\"gr\" AS name"),
DB::raw("\"1\" AS gram_weight"),
]));
});
}
This is to add one record. To add more, simply chain more union functions, or edit the query inside.
Note: For Laravel 6.x use "boot" instead of "booted", and add a line parent::boot(); before addGlobalScope

Laravel GroupBy with Sum added to Object

I searched all other questions before. I have to simple groupBy select and get sum out of column. But how to make 1 query out of this ( without merge ). Possible?
$Todo = Todo::selectRaw('sum(estimated_time) as amount')->groupBy('user_name')->get();
$Todo = Todo::get()->groupBy('user_name');
I would suggest you avoid using any raw SQL statements in Laravel.
If your goal is to get the sum of the estimated duration of all todos for each user, you can use eager loading.
For example you could first query all your users and eager load the todos.
$users = User::query()->with('todos')->get();
And then you can retrieve the sum of the estimated duration for all todos.
foreach($users as $user) {
$user->totalEstimatedTodoTime = $user->todos->sum('estimated_time')
}
If you use the total estimated todo time of a user often. You could even define an accessor
For example in your user model:
public function getTotalEstimatedTodoTimeAttribute() {
return $this->todos->sum('estimated_time');
}
Then you can retrieve the value like this:
$user->totalEstimatedTodoTime
Write this code in Model :
public function setXXXAttribute($value)
{
$this->XXX= Model::where('user_name' , $this->user_name)->sum('estimated_time');
}
public function getXXXAttribute($value)
{
return $this->XXX
}

Query Builder filter for multi level deep relationship in Laravel

I have a selection of plots which each belong to a development by a hasManyThrough relationship through housetypes. I want to filter these by development on their overview page. Plots has a housetype_id column and housetypes has a development_id column.
public function plots()
{
return $this->hasManyThrough(Plot::class, Housetype::class);
}
When I use my filter it returns the developments ID number as $development, I then need this to only show plots which are linked to that development.
I have looked into using whereHas or Join methods but have been unable to figure this out. Current filter scope is below. Thanks
public function scopeFilterDevelopment($query)
{
$development = request()->input('filter_development');
if ($development == "") {
return;
}
if(!empty($development)){
$query->where('development_id', $development);
}
}
If I can understand it right you wish to assert a condition on other Model, HasMany will load all the objects to the related model once the query is completed. Eloquent then binds the related model objects to each.
Try joins from Laravel instead. I feel this is what you exactly want: https://laravel.com/docs/5.8/queries#joins
I would use whereHas to filter the relationship:
YourModel::whereHas('plots', function($query) {
$query->filterDevelopment();
})->get();
I would also edit the query scope not to rely on the request global function, but instead pass the development of value as a parameter.
you have make a leftjon and then use when, you dont have to use
if(!empty($development)){
$query->where('development_id', $development);
}
this any more, you can use
->when($development=="" ? false : true, function($query) use ($development){
return $query->where('development_id', $development);
})
this is a full example
$queryBuilder = DB::table('facturas')->
leftJoin('clientes','clientes.id','=','facturas.clientes_id')->
select('facturas.estados_id as estado','facturas.numero as
numero',DB::raw('concat(clientes.nombre," ",clientes.apellido) as cliente'))->
when($estados===null ? false: true,function($query) use ($estados){
return $query->whereIn('facturas.estados_id', $estados);
})
It was a whereHas that solved this in the end! (another developer at work walked me through this)
Relationship -
public function housetype()
{
return $this->belongsTo(Housetype::class);
}
Function -
public function scopeFilterDevelopment($query)
{
if (request()->input('filter_development') == "") {
return;
}else{
$query->whereHas('housetype', function($housetype){
$housetype->where('development_id', request()->input('filter_development'));
});
}
}
This then returns any plot where its housetype has a matching development_id for the filter_development from the request.
Thanks for everyone's input

Laravel 5.1: Eloquent relationship hasmany, Limit records

I have a problem with Laravel 5.1: Eloquent relationship hasmany, Limit records I have 2 tables: feeds, comments. The request is to obtain 5 feeds and comments accordingly to each particular feed. I am currently using the below query:
public function getFeed($user_id){
return Feed::whereUserId($user_id)->with(['comments'])->take(10)->get()->map(function ($feed) {
$feed->comments = $feed->comments->take(5);
return $feed;
});
}
However, it returns all the comments.
My thinking was that the $feed->comments = $feed->comments->take(5); line doesn't work. I only want to get 5 comments for each feed, do you have any advise? Any comments are highly appreciated. Thank you!
It's better late than never, I was facing the same issue yesterday and ended up by sets the entire relations array on the model.
So in your case it will be like this:
return Feed::whereUserId($user_id)->take(10)->get()->map(function($feed) {
$feed->setRelation('comments', $feed->comments->take(5));
return $feed;
});
I was facing the same issue for over a month now. I just wanted to optimize the query because it was taking almost 1.5 sec to load desired data from 1 million records.
I leveraged Eloquent ORM Collection Load() method. This reduces the load time to just 300 ms.
In your case,
return Feed::whereUserId($user_id)
->get()
->each(function ($feed) {
$feed->load('comments')->take(5);
})
});
If it's something that will apply in all cases, you can adjust the relationship in the Feed model like this:
public function comments() {
return $this->hasMany('App\Comment')->limit(5);
}
$feed= Feed::whereUserId($userId)->with(['comments'])->get()->map(function ($query) {
$query->setRelation('comments', $query->comments->take(10));
return $query;
});
Feed::whereUserId($user_id)->with([
'comments' => function($query) {
// You should limit the comments by editing the
// relationships query not the main query.
$query->take(5);
}
])->take(10)->get();
This way you can limit the feed count you want and the comment count each feed has.
the take() eloquent method just adds 'limit' word at the end of the query. this type of query is more complex and isn't supported by vanilla eloquent.
fortunately, there is an additional package called eloquent-eager-limit, which helps with this problem. in order to make it work, install that package by using composer require staudenmeir/eloquent-eager-limit command and put use \Staudenmeir\EloquentEagerLimit\HasEagerLimit; line inside both parent and children model classes.
The error might be here
$feed->comments = $feed->comments->take(5);
Which should be
$feed->comments = $feed->comments->take(5)->get();
I will suggest you write in this way
$feed->load(['comments' => function($query){ return $query->take(5); }])

Resources