Append orWhere() to dynamic subquery - php

User can submit a search form with an input such as dog cat +bird. This is expected to return all Posts with title containing (dog OR cat) AND bird.
I believe I need to append dog and cat in a subquery such as :
protected function orLike(string $column, string $value): Builder
{
return $this->builder
->where(function (Builder $subQuery) use ($column, $value) {
$subQuery->orWhere($column, 'like', '%'.$value.'%'); // dog
//$subQuery->orWhere($column, 'like', '%'.$value.'%'); // cat
//$subQuery->orWhere($column, 'like', '%'.$value.'%'); etc...
});
}
above orLike is my function in a loop that runs for each parsed optional search term (dog, cat)
How do I make it so that each optional terms (dogor cat) APPEND to the $subquery with an orWhere() for each term?
Right now, obviously, it fires a new where() with a single subquery for each term.
Not sure if I am clear enough. Basically I am trying to build a rather simple search input where users can type +bird -cat dog duck meaning bird MUST be in the title AND cat MUST NOT be in the title AND (contains dog OR duck)
edit: additional info as per request in the comments
/*
usage: return $this->parseLike('title', '+dog cat -bird elephant duck');
*/
protected function parseLike(string $column, string $value)
{
// [...] irrelevant code
/*
$terms is a collection of terms, such as:
+dog
cat
-bird
elephant
duck
*/
return $terms
->unique()
->map(function (string $term) {
switch (\substr($term, 0, 1)) {
case '+':
return ['like' => \substr($term, 1)]; // function "like()" is called for terms with operator "+" such as "+dog"
case '-':
return ['notLike' => \substr($term, 1)]; // function "notLike()" is called for terms with operator "-" such as "-bird"
default:
return ['orLike' => $term]; // function "orLike()" is called for terms with no operator, such as "elephant" or "duck" or "cat"
}
})
->each(function ($combination) use ($column) {
collect($combination)
->reject(function ($term) {
return empty($term);
})
->each(function (string $term, string $operator) use ($column) {
return $this->{$operator}($column, $term);
});
});
}

Look at this example, I believe you can integrate it to your code.
All you need is to create arrays with values you 1.need, 2.don't need, 3.may be
$mustBe = ['dog', 'cat'];
$mustNotBe = ['bird'];
$mayBe = ['tiger', 'lion'];
$model = SomeModel::query();
foreach ($mustBe as $term){
$model->where('title', 'like', '%'. $term . '%');
}
foreach ($mustNotBe as $term){
$model->where('title', 'not like', '%'. $term . '%');
}
if($mayBe){
$model->where(function ($query) use ($mayBe) {
foreach ($mayBe as $term){
$query->orWhere('title', 'like', '%'. $term . '%');
}
});
}
$result = $model->get();
dd($result);
// this builder will return something like this
$result = SomeModel::where('title', 'like', '%dog%') // dog must be
->where('title', 'like', '%cat%') // cat must be
->where('title', 'not like', '%bird%') // bird must not be
->where(function ($query) {
$query->orWhere('title', 'like', '%tiger%') // tiger may be
->orWhere('title', 'like', '%lion%'); // lion may be
})
->get();

Related

How to use query result and query again using LIKE statement in Laravel

I am trying to use questions result and get question again if title has some char in it.
If I query in condition_question table, I get results as expected.
public function showQuestions($category)
{
$myArray = array($category);
$questions = Question::whereIn('question_id', function ($query) use ($myArray) {
$query->select('question_id')
->from('condition_question')
->whereIn('condition_id', $myArray);
})->orderBy('question_id', 'desc')->paginate(20);
return QuestionLiteResource::collection($questions);
}
Question: How can I use now $questions result and query again with LIKE statement. So far I tried many thing, for example like this, but something is missing as I am getting errors:
public function showQuestions($category, $queryQuestion)
{
$myArray = array($category);
$chary = $queryQuestion;
$questions = Question::whereIn('question_id', function ($query) use ($myArray) {
$query->select('question_id')
->from('condition_question')
->whereIn('condition_id', $myArray);
})->get();
$results = $questions->where('question_title', 'LIKE', "%{$chary}%")->get();
return QuestionLiteResource::collection($results->values());
}
I know it is not my best, but need some help...It would be also cool to have paginated result at the end.
So, how to get collection of questions from questions table where title has char. Any help would be most welcomed!
You might know that once you call get() function, you got the results and not able to query any further. Maybe this is gonna work:
public function showQuestions($category, $queryQuestion)
{
$myArray = array($category);
$chary = $queryQuestion;
$questions = Question::whereIn('question_id', function ($query) use ($myArray) {
$query->select('question_id')
->from('condition_question')
->whereIn('condition_id', $myArray);
})
->where('question_title', 'LIKE', "%{$chary}%")
->get();
return QuestionLiteResource::collection($questions);
}
Since you have called get() on question query, you get the result as an Laravel Collection.
To filter through collection you can use filter() function.
Example Code
$results = $questions->filter(function($question) use ($chary) {
return Str::contains($question->question_title, $chary);
});
i think you can use join():
public function showQuestions($category, $queryQuestion)
{
$myArray = array($category);
$chary = $queryQuestion;
$query = Question::getModel()->newQuery();
$questions = $query
->join('condition_question', function (Builder $join) use ($myArray) {
$join->on('questions.question_id', '=', 'condition_question.question_id');
$join->whereIn('condition_question.condition_id', $myArray);
})
->where('questions.question_title', 'like', $chary)
->orderBy('questions.question_id', 'desc')
->paginate(20)
return QuestionLiteResource::collection($questions);
}

Using Full-Text-Search in order to find partial words by laravel query builder

I would like to write search query that uses supplied phrase to search a table, return the value that matches any part of the phrase.
this code is working, but results are nothing. For example, table have 'abcde', and I searched 'bcd', result is nothing.
protected function fullTextWildcards($term)
{
return str_replace(' ', '*', $term) . '*';
}
public function index(Request $request, $slug = null)
{
$query = $slug
? \App\Tag::whereSlug($slug)->firstOrFail()->articles()
: new \App\Article;
if ($keyword = request()->input('q')) {
$raw = 'MATCH(title,content) AGAINST(? IN BOOLEAN MODE)';
$query = $query->whereRaw($raw, [$this->fullTextWildcards($keyword)]);
}
$articles=$query->latest()->paginate(10);
return view('articles.index',compact('articles'));
}
How to chain everything together so I achieve the desired result?
Thanks in advance.
You can just use like in your query to get any matches in a given column. Try this:
public function index(Request $request, $slug = null)
{
$filter = ['keyword' => $request->q , 'slug' => $slug];
$articles = \App\Tag::where(function ($q) use ($filter){
//you can leave this I just put the so the code seems clean
if ($filter['slug'] !== null) {
$q->whereSlug($slug);
}
if ($filter['keyword']) {
$keyword = $this->fullTextWildcards($filter['keyword']);
//you can use **like** this
$q->where('title', 'like', "%".$keyword."%")->orWhere('content', 'like', "%".$keyword."%");
}
})->latest()->paginate(10);
return view('articles.index',compact('articles'));
}
Hope this helps

Eloquent: Constraining an eager load relation where relation property is 0 or relation doesn't exist

I would like to get all the Report models where the relation ReportUpload's property of status equals 0 or where the ReportUpload relation doesn't exist. The Report and ReportUpload models have a one to one relationship, ReportUpload belongs to a Report.
Somewhat unsure how to go about this using eloquent's relationship constraints or any other method. Any help would be appreciated.
Here's my current code:
// initial query
$reports = Report::whereHas('link', function($query) {
$query->where('status', 'complete');
})->with('student', 'course', 'institution', 'reportUpload');
// apply constraint
if ($request->has('uploadStatus')) {
$uploadStatus = $request->has('uploadStatus'); // 0 or 1
if ($uploadStatus === 0) {
$reports = $reports
->whereDoesntHave('reportUpload')
->orWhereHas('reportUpload', function($query) use ($uploadStatus) {
$query->where('status', $uploadStatus);
});
} else {
$reports = $reports->whereHas('reportUpload', function($query) use ($uploadStatus) {
$query->where('status', $uploadStatus);
});
}
}
The code does not produce the desired results.
Edit
Trying this approach but not sure if it's correct:
$reports = $reports
->where(function ($query) use ($uploadStatus) {
$query
->whereDoesntHave('reportUpload')
->orWhereHas('reportUpload', function($query) use ($uploadStatus) {
$query->where('status', $uploadStatus);
});
});
First, there are some mistakes in your initial code.
1 - You're checking if the request has an uploadStatus. Then, $uploadStatus == $request->has which will always be true.
if ($request->has('uploadStatus')) {
$uploadStatus = $request->has('uploadStatus');
So I guess you might want:
if ($request->has('uploadStatus')) {
$uploadStatus = $request->input('uploadStatus');
2 - You're comparing strictly $uploadStatus === 0 which might not work because the request might return a string '0' and not an integer, so you should either compare with == or cast $uploadStatus to (int).
After this, I think the code you added in your question works as expected:
$reports = $reports
->where(function ($query) use ($uploadStatus) {
$query
->whereDoesntHave('reportUpload')
->orWhereHas('reportUpload', function($query) use ($uploadStatus) {
$query->where('status', $uploadStatus);
});
});
Because the where encapsulating the query will put it between parentheses.
Try to separate the queries. whereDoesntHave might be counting negatively with the orWhereHas even if it is an or statement:
$reportsNoUpload = $reports
->whereDoesntHave('reportUpload')->get();
$reportsIncomplete = $reports
->orWhereHas('reportUpload', function($query) use ($uploadStatus) {
$query->where('status', $uploadStatus);
})->get();
$reports = $reportsNoUpload->merge($reportsIncomplete);

Laravel display relation results

From the URL I am getting either a $filter, $month or both. When I for example want to show only results for November, everything works fine using the code below. But, when a filter has been set ALSO, then it shows all results (also from other months), if the filter has been found in the database.
What I want to achieve is, show only the results if the filter that was set has been found in the database, AND if a month has been set, only show the results from that particular month also.
My Section and Expense models have a belongsTo and hasMany relation respectively.
$query = Section::query();
If a filter has been set:
if ($filter !== 'false') {
$query->with(['expenses' => function ($query) use ($month) {
$query->where('date', 'LIKE', '%2015-' . Carbon::parse($month)->format('m') . '%');
}]);
}
If a month has been set:
if ($month !== 'false') {
$query->with(['expenses' => function ($query) use ($filter) {
$query->where('type', '=', $filter);
}]);
}
I also tried the following code:
if ($filter !== 'false') {
$query->with('expenses')->whereHas('expenses', function($query) use ($filter) {
$query->where('type', '=', $filter);
});
}
if ($month !== 'false') {
$query->with('expenses')->whereHas('expenses', function($query) use ($month) {
$query->where('date', 'LIKE', '%2015-' . Carbon::parse($month)->format('m') . '%');
});
}
$expenses = $query->orderBy('section', 'asc')->paginate(25);
I use a foreach on the $expenses in my view to display the results.
With the way you have it, you're overwriting one eager loading constraint with another. You need to move all your checks into one eager loading constraint, like so:
$query->with(['expenses' => function ($q) use ($month, $filter) {
if ($month !== 'false') {
$q->where('date', 'LIKE', '%2015-' . Carbon::parse($month)->format('m') . '%');
}
if ($filter !== 'false') {
$q->where('type', '=', $filter);
}
}]);
You need to combine both, else the last one is messing with the state set by the first eloquent model.
For a quick fix and understand whats happening try:
if ($filter !== 'false') {
$query = $query->with('expenses')->whereHas('expenses', function($query) use ($filter) {
$query->where('type', '=', $filter);
});
}
if ($month !== 'false') {
$query = $query->with('expenses')->whereHas('expenses', function($query) use ($month) {
$query->where('date', 'LIKE', '%2015-' . Carbon::parse($month)->format('m') . '%');
});
}
$expenses = $query->orderBy('section', 'asc')->paginate(25);
In the correct scenenario you should handle all AND conditions inside the inner closure.
it can be done using
$query->where(condition1);
$query->where(condition2);
or
$query->where(condition1)
->where(condition2);
Take a look to this answer and this documentation

How can I build a condition based query in Laravel?

I can do this in Code Igniter:
$this->db->select();
$this->from->('node');
if ($published == true)
{
$this->db->where('published', 'true');
}
if (isset($year))
{
$this->db->where('year >', $year);
}
$this->db->get();
How can this code be translated so that it works in Laravel?
In Fluent you can do:
$query = DB::table('node');
if ($published == true)
$query->where('published', '=', 1);
if (isset($year))
$query->where('year', '>', $year);
$result = $query->get();
As of Laravel 5.2.27, you can avoid breaking the chain by writing your conditions as so:
$query = DB::table('node')
->when($published, function ($q) use ($published) {
return $q->where('published', 1);
})
->when($year, function($q) use ($year) {
return $q->where('year', '>', $year);
})
->get();
To use Eloquent,just swap $query = DB::table('node') with Node:: but realize if both conditions fail, you'll get everything in the table back unless you check for some other condition before querying the db/model or from within the query itself.
Note the that $published and $year must be in local scope to be used by the closure.
You can make it more concise and readable by creating a macro. See: Conditionally adding instructions to Laravel's query builder
Here is how you can accomplish your query:
$year = 2012;
$published = true;
DB::table('node')
->where(function($query) use ($published, $year)
{
if ($published) {
$query->where('published', 'true');
}
if (!empty($year) && is_numeric($year)) {
$query->where('year', '>', $year);
}
})
->get( array('column1','column2') );
To find more information, I recommend reading through Fluent and Eloquent in the Laravel docs.
http://laravel.com/docs/database/fluent
I have not seen it here. You can even start your query like
$modelQuery = Model::query();
and then chain other query command afterwards. Maybe it will be helpful for someone new.
You can use Model::when() in Condition or you can create Builder::micro()
For Example
$results = Model::where('user_id', Auth::id())
->when($request->customer_id, function($query) use ($request){
return $query->where('customer_id', $request->customer_id);
})
->get();
If You need to create micro for a condition then. follow below instruction.
Write thic code in your serverice provider
Builder::macro('if', function ($condition, $column, $operator, $value) {
if ($condition) {
return $this->where($column, $operator, $value);
}
return $this;
});
Use Like Below Example
$results = Model::where('user_id', Auth::id())
->if($request->customer_id, 'customer_id', '=', $request->customer_id)
->get();
Ref: themsaid
If you need to use Eloquent you can use it like, I'm not sure that whereNotNull is the best use but I couldn't find another method to return what we really want to be an empty query instance:
$query = Model::whereNotNull('someColumn');
if(x < y)
{
$query->where('column1', 'LIKE', '%'. $a .'%');
}else{
$query->where('column2', 'LIKE', '%'. $b .'%');
}
$results = $query->get();
This way any relationships still work, for example in your view you can still use
foreach($results as $result){
echo $result->someRelationship()->someValue;
}
There is a good amount of info on here http://daylerees.com/codebright/eloquent-queries about this sort of stuff.
In Laravel > 5.2 you can use when():
$results = DB::table('orders')
->where('branch_id', Auth::user()->branch_id)
->when($request->customer_id, function($query) use ($request){
return $query->where('customer_id', $request->customer_id);
})
->get();
Docs: https://laravel.com/api/5.8/Illuminate/Contracts/Container/Container.html#method_when
Blog post: https://themsaid.com/laravel-query-conditions-20160425/
for eloquent query i used following that executes only if where condition has value
->where(function($query) use ($value_id)
{
if ( ! is_null($value_id))
$query->where('vehicle_details.transport_type_id', $value_id);
})
We can write like this (More precise way):
$query = DB::table('node')->when($published, function ($q, $published) {
return $q->where('published', 1);
})->when($year, function($q, $year) {
return $q->where('year', '>', $year);
})->get()
Not mentioned in Laravel docs. Here is pull request.

Categories