Laravel search system based on input values - laravel

I have a search form with 4 inputs such as username, text, fromDate, toDate, and users can search with just one field , the problem is i don't know how to build a query with inputs which have values, I can compare them if each one of them has value or not like this :
if ($request->input('fromdatepicker') && $request->input('todatepicker') && $request->input('search-text')){
$query = \App\InstaPost::WhereFullTextWithTimestamp($request->input('search-text'), $from_timestamp, $to_timestamp)->paginate(12);
}else if ($request->input('search-text') && empty($request->input('fromdatepicker')) && empty($request->input('todatepicker'))){
$query = \App\InstaPost::WhereFullText($request->input('search-text'))->paginate(12);
} else if(empty($request->input('search-text')) && $request->input('fromdatepicker') && $request->input('todatepicker')){
$query = InstaPost::WhereTimestamp($from_timestamp, $to_timestamp)->paginate(12);
}
i have different scenarios:
as you can see so many scenarios,
but as you know it'll be a huge mess ! these if's is just for 3 inputs! and i should compare them for each scenarios , hope you understand the problem and help me.
I'm using Laravel-Mongodb(jessengers) and i should find out which input has value and then make a query and add them to this code block :
public function scopeWhereFullTextWithTimestamp($query,$search,$from_timestamp , $to_timestamp)
{
$query->getQuery()->projections = ['score'=>['$meta'=>'textScore']];
$query->orderBy('post.taken_at_timestamp','DESC');
return $query->whereRaw([
'$text' => ['$search' => $search],
'post.taken_at_timestamp'=> [
'$gte' => $from_timestamp,
'$lte' => $to_timestamp
]
]);
this is for search-text , fromDate and toDate inputs,

You can concatenate queries based on the input, something in this direction should do the trick.
$base_query = App\InstaPost;
if(!is_null($request->input('search-text'))){
$base_query->WhereFullText($request->input('search-text'));
}
if...
if..
$result = $base_query->get(); //or paginate()

Related

Laravel Query Builder and Eloquent query builder add an odd phrase when I try to build the query on the fly?

I am attempting to build a query on the fly depending upon what is entered in a form. When I do so, I check what the sql statement is in the Console. I have OR and AND contitions, so I use a call back to build the query. the SQL statement that results is:
select `id`, `activity_name`, `activity_name_web`, `year`, `season`, `sex`,
`active_on_web`, `begin_active_on_web`, `end_active_on_web`, `begin_age`,
`end_age`, `begin_date`, `end_date` from `activities` where (season='fall'
OR season='summer' is null) order by `begin_date` desc, `activity_name` asc
limit 25 offset 0 []
notice the words "is null". I can't determine how this phrase is being added.
The Code:
$qry_where = "1";
$arry_Season = array();
if($this->a_fall)
$arry_Season[] = "LOWER(`season`)='fall'";
if($this->a_spring)
$arry_Season[] = "LOWER(`season`)='spring'";
if($this->a_summer)
$arry_Season[] = "LOWER(`season`)='summer'";
if($this->a_winter)
$arry_Season[] = "LOWER(`season`)='winter'";
if(count($arry_Season)>0) {
$qry_where = $arry_Season[0];
if(count($arry_Season)>1){
array_shift($arry_Season);
foreach($arry_Season as $season){
$qry_where .= " OR " . $season;
}
}
}
$activities = Activity::select('id','activity_name','activity_name_web','year','season', 'sex', 'active_on_web',
'begin_active_on_web', 'end_active_on_web', 'begin_age', 'end_age', 'begin_date','end_date')
->where(function($query) use ($qry_where) {
if($qry_where == "1")
$query->where('id','>',0);
else {
$query->where(DB::raw($qry_where));
}
})
->orderBy('begin_date','desc')->orderBy('activity_name','asc')
->paginate(25);
So, if the users checks the box labelled "fall", I add it to the query builder.
so - Where is the phrase "is null" coming from.
select `id`, `activity_name`, `activity_name_web`, `year`, `season`, `sex`,
`active_on_web`,`begin_active_on_web`, `end_active_on_web`, `begin_age`,
`end_age`, `begin_date`, `end_date` from `activities`
where (season='fall' OR season='summer' is null)
order by `begin_date` desc, `activity_name` asc limit 25 offset 0 []
btw, say I check fall and summer, then I ddd($qry_where) just before I build the query, I get this:
"LOWER(`season`)='fall' OR LOWER(`season`)='summer'"
I also need to mention that I am using a livewire component.
thanks for the help in advance.
#apokryfos thank you for your quick answer. It was driving me crazy. It seems when I passed a variable into the ->where(DB::raw()), it evaluated the variable too late for the ->when() to see that the variable contained a comparison?
In any case, I tweeked the solution that #apokryfos a bit and came up with the following:
$activities = Activity::select('id','activity_name','activity_name_web','year','season', 'sex', 'active_on_web',
'begin_active_on_web', 'end_active_on_web', 'begin_age', 'end_age', 'begin_date','end_date',
'cost_member','cost_non_member')
->when(count($seasons) > 0, function($query) use ($seasons) {
$query->whereIn('season', $seasons);
})
->orderBy('begin_date','desc')->orderBy('activity_name','asc')
->paginate($this->items_on_page);
the ->whereIn() could not process the "DB::raw('LOWER(season)', $seasons)" as suggested. If i want to make sure that the data in the [activities].[season] column, I will either have to make sure the seasons are input into the table in lower case, or do a ->select(DB:raw('LOWER(season)') in the SQL statement. I like the first option.
thank you again for your input apokryfos
You did a where(<raw query>) which defaults to do a WHERE <raw query> is not NULL since WHERE assumes a comparison. Here's what I think is a more concise version of your query:
$seasons = array_filter([
$this->a_spring ? 'spring' : null,
$this->a_summer ? 'summer' : null,
$this->a_fall ? 'fall' : null
$this->a_winter ? 'winter' : null
]);
$activities = Activity::select('id','activity_name','activity_name_web','year','season', 'sex', 'active_on_web',
'begin_active_on_web', 'end_active_on_web', 'begin_age', 'end_age', 'begin_date','end_date')
->when(count($seasons) > 0, function($query) use ($seasons) {
$query->whereIn(DB::raw('LOWER(season)'), $seasons);
})->when(count($seasons) == 0, function ($query) {
$query->where('id', > 0); // Not sure you need this part
})
->orderBy('begin_date','desc')->orderBy('activity_name','asc')
->paginate(25);

Laravel query builder search by status

In my project, the user can filter the data according to 3 criteria (declared, in progress, finished).
I pass string, in the url parameters [-1,0,1], in order to obtain the active filters. the statuses are defined according to several data coming from different columns of the database, in other words the information is not stored raw in the database.
How can I retrieve filtered data using Query Builder?
$ret = array_filter(explode(',',', $args), 'is_numeric');
$declared = in_array(1, $ret);
$progress = in_array(-1, $ret);
finished = in_array(0, $ret);
if ($declared == false && $progress == false && $finished == false) {
// do the treatment for this case....
}
If I have to deal with all the cases like that, I don't think it's very optimized.
Do you have an idea?
If I understand correctly you have a url like this /your-link?status=n, where n is an int from -1 to 1.
Now, in your controller you can do something like this:
public function yourFunction(Request $request)
{
if (in_array($request['status'], [-1, 0, 1])) {
$data = DB::table('your-table')->where('status', $request['status'])->get();
}
}
I'd recommend you to take a look at pipelines and try to use them with Eloquent or Query builder.
Check out this tutorial and see for yourself how you can implement a solution.

Laravel Eloquent how to query the avrege and compare it between two values

I am looking for a solution to filte jobs basing on the salary.
I have a Job model which has 2 column min_salary and max_salary, and the user is going to use a slider to pick two values as min and max salary.
I have tried the whereBetween and orWhereBetween but the problem is the query gets closed and I can't proceed with other filters, so am trying to get the average between the model min/max salary and use it instead of the two slaries.
Here's what i tried:
$salary = explode(',',$request->salary); // geting the salary range from the request as array
$jobs = Job::whereBetween('min_salary',$salary)->orWhereBetween('max_salary',$salary);
This solution does not work as I want, as I can't proceed with other filters.
I also tried to do a custom orWhere function and do seperated whereBetween queries, but I got the same result.
What I am trying now is to get the average between the model max and min salary without creating another field*, and then proceed to do something like that :
$jobs = Job::whereBetween('theCalculatedAvg',$salary);
I appreciate any help with any kind of solution that does not require creating another field in the database, and i wont mind an sql raw solution if it does the job.
Thank you.
Update
Heres the whole function u had :
if($request->has('offset')) {
$salary = explode(',',$request->salary);
$jobs = Job::whereBetween('min_salary',$salary)->orWhereBetween('max_salary',$salary);
if ($request->has('lat')) {
$sqlDistance = DB::raw
('
( 6371 * acos
( cos
( radians
(' . $request->lat . ')
)
* cos
( radians
( lat )
)
* cos
( radians
( lon )
- radians
(' . $request->lon . ')
)
+ sin
( radians
(' . $request->lat . ')
)
* sin
( radians
( lat )
)
)
)
');
$jobs->when($sqlDistance != null, function ($query) use ($sqlDistance,$request){
$query->whereHas('address', function ($subQuery) use ($sqlDistance,$request) {
$subQuery->addSelect(DB::raw("{$sqlDistance} AS distance"));
$subQuery->havingRaw("distance <= ?", [(int)$request->range]);
});
})
->with('company')
->with('address');
}
if ($request->has('key')) {
$jobs->where('title', 'like', '%' . $request->key . '%');
}
if ($request->has('cat')) {
$cat = explode(',',$request->cat);
$jobs->whereIn('category_id', $cat);
}
if ($request->has('type')) {
$type = explode(',',$request->type);
$jobs->whereIn('type', $type);
}
if($request->has('hs')) {
return view("General::browseJobs", [
'jobs' => $jobs->orderBy('created_at','desc')->skip($request->offset * 2)->take(2)->get(),
'count' => count($jobs->get())
]);
}
$view = view('General::loaders.jobs', [
'jobs' => $jobs->orderBy('created_at','desc')->skip($request->offset * 2)->take(2)->get()
])->render();
return response()->json(['html' => $view , 'count' => count($jobs->get()) ]);
}
return view("General::browseJobs", [
'jobs' => Job::orderBy('created_at','desc')->take(2)->get(),
'count' => count(Job::all())
]);
The issue is that i just want the salary filter to work this way:
lets say the user picked twho values : $min and $max (will be stored in an array)
now i want to show him the jobs where the min_salary is between [$min, $max]
or the max_salary is between [$min, $max].
NB : if i use :
Job::whereBetween('min_salary',$salary)->whereBetween('max_salary',$salary);
without the or it works just fine, but i want the or logic to be implemented.
You need to scope the or to those 2 conditions only:
Jobs::where(function ($q) use ($salary) {
$q->whereBetween('min_salary',$salary)->orWhereBetween('max_salary',$salary);
})
To understand why this is required, you need to consider the sql query produced by eloquent.
If you write:
$jobs = Jobs::where([condition1])->orWhere([condition2]);
.. some other code..
$jobs->where([condition3]);
...
$jobs->where([condition4]);
The resulting query will be:
SELECT *
FROM jobs
WHERE [condition1] OR [condition2] AND [condition3] AND [condition4]
But in SQL, the AND operator has precedence over the OR, so the conditions are logically considered like this:
([condition1]) OR ([condition2] AND [condition3] AND [condition4])
This is not the behavior you want.
By scoping the OR condition in a where closure, you basically tell eloquent to add a parenthesis around the conditions, so:
$jobs = Jobs::where(function ($q) {
$q->where([condition1])->orWhere([condition2]);
});
.. some other code..
$jobs->where([condition3]);
...
$jobs->where([condition4]);
Results in the following query:
SELECT *
FROM jobs
WHERE ([condition1] OR [condition2]) AND [condition3] AND [condition4]
which is the desidered one
Ok, thank you for updating the question. I'm pretty sure I'm not able to answer it in one go, but I'll do my best.
As far as I can see, you are working on a Job site where you want to allow users to enter filters, and only show jobs matching those filters.
I see you have filters for min/max salary, distance, title, categories and types.
You also have a hs key which decides which view to open with which data.
I would consider the min/max salary to be an OR statament, and the rest an AND.
In that case I would do
$mainQuery = Job::where(function($query) use($salary)
{
$query->whereBetween('min_salary', $salary)->orWhereBetween('max_salary', $salary);
});
So you can do $mainQuery->where('anothermust-have', $somevalue);

Count number of same values in JSON array and convert it to string

My JSON looks something like this:
[
{"pet_type":"Dog","weight":"26","description":"Akita"},
{"pet_type":"Dog","weight":"6","description":"Pug"},
{"pet_type":"Cat","weight":"4","description":"Manx"},
{"pet_type":"Dog","weight":"12","description":"Beagle"},
{"pet_type":"Cat","weight":"5","description":"Siberian"}
]
How could I convert it to a string which would look like3 Dogs, 2 Cats?
The way I tried is filling an array with pet_type and than use array_count_values to count number of same records, and later I go through that array in a foreach and concat string like this:
foreach ($count_animals as $type => $number) {
$animals .= $number.' '.str_plural($type, $number).', ';
}
This works, but my question is, could I do it with less code, directly from JSON, without using one more foreach loop?
If it works, you can keep your code.
If you want less code, you can use this version :
$json = '[
{"pet_type":"Dog","weight":"26","description":"Akita"},
{"pet_type":"Dog","weight":"6","description":"Pug"},
{"pet_type":"Cat","weight":"4","description":"Manx"},
{"pet_type":"Dog","weight":"12","description":"Beagle"},
{"pet_type":"Cat","weight":"5","description":"Siberian"}
]';
print_r(array_count_values(array_map(function($item) {
return $item['pet_type'];
}, json_decode($json, true))));
Gonna display :
Array ( [Dog] => 3 [Cat] => 2 )
in your controller
$pet = Pet::get();
$petcount = Pet::where('pet_type','Dog')->get();
In your blade
<h1>{{count($petcount)}} Dog</h1>

Reversing IQueryable based on passed property for sorting logic

I am implementing sort based on parameter passed to ascending or descending OrderBy method
else if (showGrid.Sortdir == "DESC")
{
alerts = DB.Incidents.OfType<Alert>().Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching)
.OrderByDescending(a => showGrid.Sort);
}
else
{
alerts = DB.Incidents.OfType<Alert>().Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching)
.OrderBy(a => showGrid.Sort);
}
In case of ascending order sorting it works fine but for descending order sorting doesn't work. I debugged the code and I found that list is not revered its same as ascending order. Please help me
Ok. I've written a small test. It is funny, but your code can actually compile and work, but very differently from what you expect :)
Obviously showGrid is not of type Alert, it is an instance of some other class, that incidentally have the same propery as Alert, called Sort.
First I was confused, because expected this code to fail to compile.
// The signature of OrderBy
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
// In your case it will result in
public static IOrderedQueryable<Alert> OrderBy<Alert, string>(this IQueryable<Alert> source, Expression<Func<Alert, string>> keySelector)
//when you call it like you do
DB.Incidents.OfType<Alert>().OrderByDescending(a => showGrid.Sort);
// You supply a property from object of type different from your entity.
// This is incorrect usage, the only object you can use here is the
// "a" argument. Like this:
DB.Incidents.OfType<Alert>().OrderByDescending(a => a.Sort);
// Because anything else does not make any sense to entity provider.
So your order by simply does not work.
As far as I understood, what you want is to perform sorting based on selection in UI. This is not easily achieved in strongly-typed LINQ. Because as I showed above, you send a property, not a value to the OrderBy. It does not care about the value inside the prop. So there are several solutions to the problem:
Write a big switch, that will check every possible Sort value, and will append appropriate 'OrderBy(a => a.YouPropToSort)' to the query. This is straitforward, and you should begin with this. Of course this is a static way, and will require to change code everytime you want new columns to be added for sorting.
Create argument for your OrderBy using 'LINQ Expression Trees'. For you case it should not be very hard to do. Look for the term, you will find a lot of examples.
Try to use Dynamic LINQ. I did not not use it myself, just looked at the docs. This seems to be an extension to the normal LINQ which allows you to write parts of queries as strings, to overcome limitations like the current one with dynamic sorting.
Here's my solution to sorting based on user selections:
Create your base query
var query = DB.Incidents.OfType<Alert>.Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching);
and then apply your sort using a case statement
bool desc = showGrid.SortDir = "DESC";
switch(showGrid.Sort)
{
case "col1":
query = desc ? query.OrderByDescending( a => a.Col1 ) : query.OrderBy( a => a.Col1 );
break;
case "col2":
query = desc ? query.OrderByDescending( a => a.Col2 ) : query.OrderBy( a => a.Col2 );
break;
...
}
var results = query.ToList();

Resources