Using WhereRaw on collection ignores relation - laravel

I made a route to search a particular collection - Customers.
Customer Model
public function location() {
return $this->belongsTo('App\Location');
}
Location Model
public function customers() {
return $this->hasMany('App\Customer');
}
On the index page, I'm simply showing the customers with the data from $location->customers()
$location comes from the model route binding.
This is my search controller:
if ($request->input('search') != null) {
$customers = $location->customers();
$search = strtoupper($request->input('search'));
$searchQuery = 'UPPER(email) LIKE (?) OR CONCAT(UPPER(first_name), " ", UPPER(last_name)) LIKE (?)';
$customers = $location->customers()->whereRaw($searchQuery, ["%{$search}%", "%{$search}%"]);
$customers = $customers->paginate();
}
return response()->json(['results' => $customers], 200);
When a search is executed, I get 10 times as many results in some cases because it's grabbing all customers rather than the specific location relationship.
How can I make whereRaw use the relation?

It is because the OR condition messes up with the query. To avoid that wrap those two query parts with brackets ().
$searchQuery = '( UPPER(email) LIKE (?) OR CONCAT(UPPER(first_name), " ", UPPER(last_name)) LIKE (?) )';
Eloquent builder approach:
$customers = $location->customers()->where(function($query) use ($search) {
$query->whereRaw('UPPER(email) LIKE (?)', ["%{$search}%"]);
$query->orWhereRaw('CONCAT(UPPER(first_name), " ", UPPER(last_name)) LIKE (?)', ["%{$search}%"]);
});
Explanation
Example logical expression:
firstCondition && secondCondition || thirdCondition
In above example expression thirdCondition does not even care about firstCondition or secondCondition because of the ||.
So if you want to check this thirdCondition with secondCondition, then you have to explicitly wrap this two conditions with brackets ().
firstCondition && ( secondCondition || thirdCondition )
This same logic applies for mysql queries too.

Related

How to get data HasMany() using WhereHas in Laravel

I want to get data from my table " Package " by using its model " Package "
and in this model " Package " it have a HasMany() named histories() relation to model " History "
so i want to only get data that have histories
here is my controller
public function getIncomeMPW(Request $request)
{
if ($request->expectsJson()) {
$this->getSearch($request);
$query = new Package();
$query->with(['histories', 'items', 'items.prices', 'origin_regency', 'origin_district', 'origin_sub_district', 'destination_regency', 'destination_district', 'destination_sub_district', 'code', 'attachments']);
$query->whereHas('histories', function (Builder $query) {
$query->whereNotNull('partner_id');
});
$query->orderBy('created_at', 'desc');
return (new Response(Response::RC_SUCCESS, $this->query->paginate(request('per_page', 15))))->json();
}
}
here is my Package model relation histories HasMany()
public function histories(): HasMany
{
return $this->hasMany(History::class, 'package_id', 'id');
}
and last here is my response that showing right now
i already try using whereHas(), Has(), whereDoesntHave(), and its seems like there is no impact on my response, can anyone help me please ?
In your response you simply access a different query as it seems.
return (new Response(Response::RC_SUCCESS, $this->query->paginate(request('per_page', 15))))->json();
Uses $this->query
While
$query = new Package();
$query->with(['histories', 'items', 'items.prices', 'origin_regency', 'origin_district', 'origin_sub_district', 'destination_regency', 'destination_district', 'destination_sub_district', 'code', 'attachments']);
$query->whereHas('histories', function (Builder $query) {
$query->whereNotNull('partner_id');
});
$query->orderBy('created_at', 'desc');
Defines a $query without $this. I'd expect your $this->getSearch($request); to define $this->query (as the function is not posted in the question, i cannot tell). So either remove $this in your response - or change everything to $this and ensure to now overwrite it in the first line.
Quickfix should be
return (new Response(Response::RC_SUCCESS, $query->paginate(request('per_page', 15))))->json();
UPDATE:
Quick answer: Change
return (new Response(Response::RC_SUCCESS, $this->query->paginate(request('per_page', 15))))->json();
To
return (new Response(Response::RC_SUCCESS, $query->paginate(request('per_page', 15))))->json();
Wwhat whereHas and whereDoesntHave functions do in the backstage is that they make a sub query such as:
Select * from packages where exists (select * from histories where CONDITIONS YOU HAVE MENTIONED)
And the problem here is that when you use with method you eager load table history which adds one extra query that is not related to the first one such as:
Select * from histories where package_id in (1,2,3,4,5,6)
So since we cleared that out, what I suggest you do is that you assign a function to a variable in this way:
$historyFunction = function ($query) {
$query->whereNotNull('partner_id');
};
and than call it in with and in whereHas methods as shown below:
$query->with(['histories' => $historyFunction, otherRelations... ]);
$query->whereHas('histories', $historyFunction);
And what this does is that it tells eloquent: When you eager load Histories relationship add this conditions to the query you are about to make.

Any short way for handling multiple queries in laravel?

I need to get multiple results from different queries on one table.
For example I need to get Count, Sum, Average of one table. Should I do like this or is there a shorter way?
public function index()
{
$count = Patient::all()->count();
$dateCount = Patient::where('date', date("Y-m-d"))->count();
$loanAmount = DB::table('patients')->sum('loan_amount');
$payAmount = DB::table('patients')->sum('pay_amount');
return view('index', compact('count','dateCount','loanAmount' ,'payAmount'));
}
If you see All queries are for one table to get specific results, So basically is there a short way to get these results not by single queries for each?
You can do this by DB query as below :
$data=\DB::table('patients')
->selectRaw('count(id) as count,sum(loan_amount) as loanAmount,sum(pay_amount) as payAmount,sum(case when date = "'.date("Y-m-d").'" then 1 else 0 end) AS dateCount')
->first();
You can also do this using eloquent but it will return you collection.
$data=Patient::selectRaw('count(id) as count,sum(loan_amount) as loanAmount,sum(pay_amount) as payAmount,sum(case when date = "'.date("Y-m-d").'" then 1 else 0 end) AS dateCount')
->first();
You can do something like this.
public function index()
{
$patient = Patient::all();
$count = $patient->count();
$dateCount = Patient::today()->count();
$loanAmount = $patient->sum('loan_amount');
$payAmount = $patient->sum('pay_amount');
return view('index', compact('count','dateCount','loanAmount' ,'payAmount'));
}
Also you can create scope in your patient model:
public function scopeToday($query) {
return $query->where('date', date("Y-m-d"));
}

Laravel Eloquent Accessor Strange Issue

Laravel Version: 5.6.39
PHP Version: 7.1.19
Database Driver & Version: mysql 5.6.43
Description:
When I chain where and orWhere in a model accessor to count related model , I get wrong result and here is my query. the count is returned strange result without filtering by the calling event id,
class Event extends Model
{
protected $table = 'events';
public function registrations()
{
return $this->hasMany('App\Components\Event\Models\Registration','event_id','id');
}
public function getSeatsBookedAttribute()
{
return $this->registrations()
->where('reg_status','=','Confirmed')
->orWhere('reg_status','=','Reserved')
->count();
}
}
Steps To Reproduce:
the following queries return me the expected results, however In my knowledge the first query should return the same result if i am not wrong, so i think this is a potential bug.
class Event extends Model
{
public function getSeatsBookedAttribute()
{
return $this->registrations()
->whereIn('reg_status', ['Confirmed', 'Reserved'])
->count();
}
}
class Event extends Model
{
public function getSeatsBookedAttribute()
{
return $this->registrations()
->where(function($query){
$query->where('reg_status','Confirmed')
->orWhere('reg_status','Reserved');
})
->count();
}
}
and here is the query dump,
here is the query when I donot explicit group it.
"select count(*) as aggregate from events_registration where (events_registration.event_id = ? and events_registration.event_id is not null and reg_status = ? or reg_status = ?) and events_registration.deleted_at is null "
and here is the query when i group it explicitly,
select count(*) as aggregate from events_registration where events_registration.event_id = ? and events_registration.event_id is not null and (reg_status = ? or reg_status = ?) and events_registration.deleted_at is null
The reason this happens is because you're chaining where() and orWhere(). What you don't see behind the scenes is a where event_id = :event_id applying to your query. You end up with a query that looks something like this:
select * from registrations where event_id = :event_id and reg_status = 'Confirmed' or reg_status = 'Reserved'
In normal SQL you'd want to put the last 2 conditions in parentheses. For Eloquent, you'd need to do something like this:
return $this->registrations()->where(function ($query) {
$query->where('reg_status', 'Confirmed')
->orWhere('reg_status', 'Reserved');
});
You can chain the toSql() method on these chains to see the difference. Note, that in this case, I believe whereIn() is the semantically correct thing to do.
Eloquent can handle this for you, though; scroll down to "Counting Related Models" in the Querying Relations part of the Eloquent Relationships docs:
$posts = App\Event::withCount([
'registrations as seats_booked_count' => function ($query) {
$query->where('reg_status','Confirmed')
->orWhere('reg_status','Reserved');
}
])->get();

Laravel include relationship result if one exists

I am trying to write a query where all items are returned (products) and if a relationship exists for that particular item (many to many) then that information is included too. When I include the relationship at the moment on the query it only returns items that have that relationship rather thatn every single item, regardless of whether that relationship exists or not.
Here is my query at the moment:
public static function filterProduct($vars) {
$query = Product::query();
if((array_key_exists('order_by', $vars)) && (array_key_exists('order', $vars))) {
$query = $query->orderBy($vars['order_by'], $vars['order']);
}
if(array_key_exists('category_id', $vars) && $vars['category_id'] != 0) {
$query = $query->whereHas('categories', function($q) use ($vars) {
return $q->where('id', $vars['category_id']);
});
}
if(array_key_exists('manufacturer_id', $vars)) {
$query = $query->whereHas('manufacturer', function($q) use ($vars) {
return $q->where('id', $vars['manufacturer_id']);
});
}
$query = $query->whereHas('options', function($q) use ($vars) {
});
As you can see, when an item has the 'options' relationship I need to have that particular row include details of that relationship in the returned date. With the code as it is though it is only returning items that have this relationship rather than every single item.
Can someone advise me as to how this is achieved please?
Thanks!
I feel a bit stupid as it was so simple but it was solved by adding this:
$query = $query->with('options');

Where to write search logic in laravel

I am writing a search script and want to return results based on the user's query.
Following is my route :-
Route::get('/search/{city}/{searchquery}', 'SearchController#search');
Controller
public function search($city, $query){
strtolower($query);
$commonWords = array('a','able','about','above','abroad'.....);
$cleanQuery = preg_replace('/\b('.implode('|',$commonWords).')\b/','',$query);
$cleanQuery = $s = preg_replace('/[^a-z0-9]+/i', ' ', $cleanQuery);
$queryarray = explode(' ',$cleanQuery);
$queryarray = array_filter( $queryarray );
$queryarray = array_slice( $queryarray, 0 );
//code to match each query word with MySQL fields such as title, description
return $result;
}
I believe, all this logic and code should not be written in the controller. What can I use to write logic and use controller to only return result
The simplest way would be to use models for that. Just write a method that gets keywords and parses it into sql query. You could use scope methods to allow for more flexibility.
public function scopeSearch($q, $keywords)
{
// ... $keywords processing here
$keywordsArray = explode(',', $keywords);
return $query->whereIn('keyword', $keywordsArray);
// OR maybe something like this?
return $query->where('keywords', 'LIKE', '%'. $keywords .'%');
}
You could use this method in a controller like that:
$city = new City();
$results = $city->search($keywords)->where(/* additional conditions could be added here */)->get();
Even if you decide not to use scope methods, I would still advise to return query builder objects instead of collections as to allow for better flexibility, such as implementing pagination or additional manipulation in controllers where needed.
You could also use Repositories if you are not afraid to add more complexify to gain more flexibility. More on that here: https://laracasts.com/lessons/repositories-simplified

Resources