Laravel Eloquent: When() on a JSONB column - laravel

I don't think I understand the proper use of when().
I'm trying to use it in this fashion:
I have a jsonb column (called info) which has multiple properties. I want to check if one of the values is 'Chemical' and perform some query. Otherwise, perform another query. This is how I am using it:
$query->when(
'info->type' === 'Chemical',
function ($query) {
return $query->has('bottles', 0);
},
function ($query) {
return $query->where('info->quantity', '<=', 0);
}
);
The first function would be the truthy value and the second function would be falsey. However, it never hits the first function.
What am I doing wrong? Is this not how you use when?

You are correct, the only error is that info->type is never going to be equal to Chemical because you are comparing strings.
So, whatever you write as the expression, it must literally be something that has nothing to do with the query.
when works as an alias of an if, so you don't need to break your query like this:
$query = Model::query();
if ($expression) {
$query->where('column_a', 1);
} else {
$query->where('column_a', 0);
}
$query->where('whatever', 123);
if ($expression2) {
$query->where('abc', 1);
}
Using when, it would be like this:
$query = Model::query()
->when(
$expression,
fn (Builder $query) => $query->where('column_a', 1),
fn (Builder $query) => $query->where('column_a', 0),
)
->when(
$expression2,
fn (Builder $query) => $query->where('abc', 1)
);
Following your example, you will need to get what info->type is, let's say you are able to get that from another part:
$infoType = 'value';
$query = Model::query()
->when(
$infoType === 'Chemical',
function ($query) {
return $query->has('bottles', 0);
},
function ($query) {
return $query->where('info->quantity', '<=', 0);
}
);
More about when on the documentation.
You can read more by just checking the when()'s source code (Laravel 9)

Related

how to use whereHas in laravel

I am new to laravel,
I need to create a query for the db,
$query = Deal::query();
I want to use the wherehas operator.
this is my code that is worked.
else if ($group_by == 'precedence') {
if($get_deals_for == 'noprecedence'){
$get_deals_for = 0;
}
$precedenceStatus = $get_deals_for;
$query-> where('precedence', '=', $precedenceStatus);
// \Log::info('The request precedence: '.$precedenceStatus);
}
I want to add this code also to the query
if($person) {
$query->whereHas('personnel', function ($subQuery) use ($person) {
$subQuery->where('id', '=', $person);
});
}
So I need to change the first code?
how I can convert the first code to wherehas?
the first code is from table called deal, the second section is from realtionship called personnel.
the second section worked in other places in the code, I just need to fix the first section and not understand what to write in the use
I try this and get error on the last }
else if ($group_by == 'precedence') {
if($get_deals_for == 'noprecedence'){
$get_deals_for = 0;
}
$precedenceStatus = $get_deals_for;
$query-> where('precedence', '=', $precedenceStatus)
-> when ($person, function($query) use($person) {
$query->whereHas('personnel', function ($query) use ($person) {
$query->where('id', '=', $person);
});
})
}
There is a method you can use called when(, so that you can have cleaner code. The first parameter if true will execute your conditional statement.
https://laravel.com/docs/9.x/queries#conditional-clauses
$result = $query
->where('precedence', '=', $precedenceStatus)
->when($person, function ($query) use ($person) {
$query->whereHas('personnel', fn ($q) => $q->where('id', '=', $person));
})
->get();
You should also be able to clean up your precedence code prior to that using when( to make the entire thing a bit cleaner.
Querying to DB is so easy in laravel you just need to what you want what query you want execute after that you just have to replace it with laravel helpers.Or you can write the raw query if you cant understand which function to use.
using,DB::raw('write your sql query').
Now Most of the time whereHad is used to filter the data of the particular model.
Prefer this link,[Laravel official doc for queries][1] like if you have 1 to m relation ship so u can retrive many object from one part or one part from many object.like i want to filter many comments done by a user,then i will right like this.
$comments = Comment::whereHas('user', function (Builder $query) {
$query->where('content', 'like', 'title%');
})->get();
$comments = Here will be the model which you want to retrive::whereHas('relationship name', function (Builder $query) {
$query->where('content', 'like', 'title%');
})->get();
you can also write whereHas inside whereHas.
[1]: https://laravel.com/docs/9.x/eloquent-relationships#querying-relationship-existence

How to use `whereDoesntHave` in laravel

I am trying to fetch users who don't have productions by checking the start date and end date ranges.
$production = Production::find($id);
$users = User::whereDoesntHave('productions', function ($query) {
return $query
->where(function ($q) {
return $q->whereBetween('start_at', [$production->start_at, $production->end_at])
->whereBetween('end_at', [$production->start_at, $production->end_at]);
});
})->get();
//User Production relation
public function productions()
{
return $this->belongsToMany(Production::class)
->withPivot(['created_at', 'updated_at']);
}
This returns users who are already assigned in other productions within current production start date and end date that and I only want to fetch users who has no production assignment within the current date range.
If I understand correctly, you have a Production model instance in the $production variable. You want all users that doesn't have production that overlap with the time range of the $production instance ?
Your current code (pasted from your question):
$production = Production::find($id);
$users = User::whereDoesntHave('productions', function ($query) {
return $query
->where(function ($q) {
return $q->whereBetween('start_at', [$production->start_at, $production->end_at])
->whereBetween('end_at', [$production->start_at, $production->end_at]);
});
})->get();
What you should do :
$production = Production::find($id);
$users = User::whereDoesntHave('productions', function ($query) use($production) {
return $query->whereBetween('start_at', [$production->start_at, $production->end_at])
->orWhereBetween('end_at', [$production->start_at, $production->end_at]);
});
})->get();
I think the key thing here is include both start and end range in the result vith a orWhereBetween condition instead of whereBetween. And don't forget to use the $production variable in the closure ;-)
If this is not what you are trying to do, please comment to explain more precisely what you want to do
You're passing the dates as parameters but you never actually use them,
this is how it should be:
$users = User::whereDoesntHave('productions', function ($query) use ($startDate, $endDate) {
return $query->where(function ($q) use ($startDate, $endDate) {
return $q->whereBetween('start_at', [$startDate,$endDate])
->orWhereBetween('end_at', [$startDate, $endDate]);
});
})
->get();

I am getting data from my database using Datatable, but it takes too much time to load

I am getting data from products table, i have use paginate in datatable which shows only 50 records first time, and when pager update to 200 rows it takes 12 to 13sec. Here is my Code. Thanks in Advance
$query = Product::query();
$query->with('def_or_last_supplier','units','prouctImages','productType',
'productBrand','productSubCategory',
'supplier_products','productCategory')
->where('status',1)->orderBy('refrence_no', 'DESC');
if($request->default_supplier != '')
{
$supplier_query = $request->default_supplier;
$query = $query->whereIn('id', SupplierProducts::select('product_id')->where('supplier_id',$supplier_query)->pluck('product_id'));
}
if($request->prod_type != '')
{
$query->where('type_id', $request->prod_type)->where('status',1)->orderBy('refrence_no', 'DESC');
}
if($request->prod_category != '')
{
$query->where('category_id', $request->prod_category)->where('status',1)->orderBy('refrence_no', 'DESC');
}
if($request->filter != '')
{
if($request->filter == 'stock')
{
$query = $query->whereIn('id',WarehouseProduct::select('product_id')->where('current_quantity','>',0.005)->pluck('product_id'));
}
elseif($request->filter == 'reorder')
{
$query->where('min_stock','>',0);
}
}
Your query isn't that big but I think maybe result data size is a bit big
so what you can do is:
1: in my experience is to try to select your relations fields too you can do that like so
Source
Product::with('def_or_last_supplier',
'units:id,created_at,updated_at', // Just add :field_1,field_2,another_field
'prouctImages:id,field_1,field_2'
....
)
2: I suggest you to take advantage of lazy loading. if it's possible don't eager load all your relations at once load them where it's needed
Source
$products = Product::get()
In blade or somewhere in application
under a special condition I need to access to my relation, I can easily do that by
$products->load('my_relation_name')
3: I see a lot of orderyBy clauses in your code, what you can do is to index the fields that are searchable or needs to be ordered. it can be done by adding ->index() in your migration file
Source
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('refrence_no')->index();
$table->timestamps();
});
}
4: and finally you can always try to cache your queries to prevent unnecessary queries to database, this is how cache works
source
$products = Cache::remember('cache_name', $how_long_in_seconds, function () {
return Product::get();
});
And finally here is the eloquent way of your query
$query = \Cache::remember(json_encode($request->toArray()), 300, function () use ($request) {
return Product::with('def_or_last_supplier',
'units',
'prouctImages',
'productType',
'productBrand',
'productSubCategory',
'supplier_products',
'productCategory'
)
->where('status', 1)
->orderByDesc('refrence_no')
->when($request->filled('default_supplier'), function (Builder $query) use ($request) {
$productIds = SupplierProducts::select('product_id')->where('supplier_id', $request->input('default_supplier'))->pluck('product_id');
$query->whereIn('id', $productIds);
})->when($request->filled('prod_type'), function (Builder $query) use ($request) {
$query->where('type_id', $request->input('prod_type'))->where('status', 1)->orderByDesc('refrence_no');
})->when($request->filled('prod_category'), function (Builder $query) use ($request) {
$query->where('category_id', $request->input('prod_category'))->where('status', 1)->orderByDesc('refrence_no');
})->when($request->filled('filter'), function (Builder $query) use ($request) {
$query->when('stock' === $request->input('filter'), function (Builder $query) {
$query->whereIn('id', WarehouseProduct::select('product_id')->where('current_quantity', '>', 0.005)->pluck('product_id'));
})->when('reorder' === $request->input('filter'), function (Builder $query) {
$query->where('min_stock', '>', 0);
});
})->get();
});

Laravel remove parent if related collection

I have a model with a related collection
now im doing this query
$data = DeliveryPartner::when($filter, function ($q) use ($request) {
})
->with(['orders' => function ($query) {
$query
->where('delivery_partner_invoice_id', '=', '')
->orWhereNull('delivery_partner_invoice_id')
->whereIn('status', ['payment-accepted', 'completed', 'full-refund', 'partial-refund']);
}])->get();
Now i am wondering. If the orders returns empty is it posible to remove this parent from the collection?
I Know i can do this after the eloquent query with a loop. But is it possible to do this in the query?
we cant completely remove that parent ( with index ) BUT you can set those to null using transform() like this;
$data = DeliveryPartner::when($filter, function ($q) use ($request) {
})
->with(['orders' => function ($query) {
$query
->where('delivery_partner_invoice_id', '=', '')
->orWhereNull('delivery_partner_invoice_id')
->whereIn('status', ['payment-accepted', 'completed', 'full-refund', 'partial-refund']);
}])->get()->transform(function($item){
if(!$item->orders->count() ){
return;
}
return $item;
});
Note: this will not completely remove those parents but it will set them to empty.

Query Builder with multiple entry points

I have different filters I want to use for my model, but they are all optional.
if($foo) {
$model = Model::where('id', 1);
}
if($bar) {
$model = $model->where('age', 3);
}
So this code will only run if the first statement will success.
$model = Model::where('id', '<>', -1);
if($foo) {
$model->where('id', 1);
}
if($bar) {
$model->where('age', 3);
}
This would work, but it's dirty :(
So is it possible to save the Model to a variabel so I don't have to make a static call inside all if statements?
https://laravel.com/api/5.4/Illuminate/Database/Eloquent/Model.html#method_query
https://laravel.com/api/5.4/Illuminate/Database/Query/Builder.html#method_when
Model::query()->when($foo, function ($query) {
$query->where('id', 1);
})->when($bar, function ($query) {
$query->where('age', 3);
});
You could make your filters in a where function, this will group them all together nicely.
For example:
$models = Model::where(function ($query) use ($request) {
$query->where('id', '<>' -1);
if ($request->has('id')
$quer->where('id', $request->id);
if ($request->has('age'))
$query->where('age', $request->age);
})->get();

Resources