Retrieve conditions matched on laravel model query - mysql

This might be an SQL limitation but I'm working on a Laravel project and so I'm posing the questions to solve what I'm trying to accomplish.
I am preforming a search on a model (and it's relationships)
Auth::user()->employers()
->where('name', 'like' $filter)
->orWhereHas('locations.location', function($query) use ($filter) {
$query->where('name', 'like', $filter);
})
->orWhereHas('locations.branches', function($query) use ($filter) {
$query->where('name', 'like', $filter);
})
->orWhereHas('locations.positions', function($query) use ($filter) {
$query->where('name', 'like', $filter);
})->get();
I would like to be able to identify which of this conditions was the one that matched the record so that I can tell the frontend, "Hey this record is being shown to you because x property nested in it matched your search critera." Where x is the condition that made it a match.
The code above returns a collection of records that matched.
I want to know where EACH of those records matched.

Now your query looks like this:
select *
from employees
where name like ?
or exists (select * from locations ...)
or exists (...)
When you add the subqueries to the SELECT part, you get boolean values:
select *,
exists (select * from locations ...) as hasLocations
exists (...) as hasBranches
...
from employees
where name like ?
or exists (select * from locations ...)
or exists (...)
You have to take a look at the whereHas implementation to find a way generating these subqueries from the relationships.

If I understand your question correctly, you simply want to show your model and say "you are seeing this because...". If that is indeed the case and the model may only have a location, or a branch, or a position when the query is complete then you may simply query it in an if statement. Assuming your 'search' criteria is $name:
$matches = array();
if($user->location == $name) {
array_push($matches, 'location');
};
if($user->branch == $name) {
array_push($matches, 'branch');
};
if($user->position == $name) {
array_push($matches, 'position');
};
return $matches;
You have basically done your query, and now need simply examine the object for which parameter satisfies your condition. If there are more than a few hard conditions though I'd search out a more object-oriented approach, though in this case a simple procedural method on the object would do. Could do this on the User model returning an array with matched values if there are more than one:
public function foundBy($name) {
$matches = array();
if($this->location == $name) {
array_push($matches, 'location');
};
if($this->branch == $name) {
array_push($matches, 'branch');
};
if($this->position == $name) {
array_push($matches, 'position');
};
return $matches;
}
This to call it:
$foundBy = $user->foundBy($name);
And should return an array.
In this way, what the method returns will tell you how the object matches by looking at the values in the array.
Hope this helped!

Related

Laravel Query builder where() for when Product has to have multiple tags (with product_tags pivot table many-to-many)

I am new to Laravel and I got a complicated query to build. I managed to sort it except for when a user asks for multiple tags (tags = 1, 2, 3). Product shown has to have all tags that the user asks (not only one or two but all of them).
I have the query in SQL (this example is two tags, I would switch it to different numbers based on how many tags are passed):
SELECT m.*
FROM meals m
WHERE EXISTS (
SELECT 1
FROM meals_tags t
WHERE m.id = t.meals_id AND
t.tags_id IN (227,25)
HAVING COUNT(1) = 2
);
This one works perfectly, but I have an issue when translating it to an Eloquent query builder where method.
I have another where method included in the same query so I want to attach this one as well.
I have tried this:
DB::table('meals')
->select('id')
->where(function ($query) use ($parameters) {
if (isset($parameters['tags'])) {
$array = explode(',', $parameters['tags']);
$query->select(DB::raw(1))
->from('meals_tags')
->where('meals.id', '=', 'meals_tags.meals_id')
->whereIn('meals_tags.tags_id', $array)
->having(DB::raw('COUNT(1)'), '=', count($parameters['tags']));
}
});
But I can't find a way. New to Laravel and PHP.
Let's say I have table meals and tags with meals_tags to connect them (many to many).
$paramaters are comming from GET (...?tags=1,2,3&lang=en&another=something...), they are an array of key-value pairs (['tags' => '1,2,3', 'lang' => 'en'])
$parameters['tags'] is of type string with comma separated numbers (positive integers greater than 0) so that I have the option to explode it if needed somewhere.
Assuming that you have defined belongsToMany (Many-to-Many) meal_tags relationship on the Meal model, you can try
Meal::select('id')
->when(
$request->has('tags'),
function($query) use ($request) {
$requestedTagIds = explode(',', $request->tags);
return $query->whereHas(
'meal_tags',
fn ($query) => $query->whereIn('tags_id', $requestedTagIds),
'=',
count($requestedTagIds)
);
}
)
->get();

Why 'where' return all data in laravel

I am applying search filter in my project so first of all I get data from multiple tables and store in two different variables and then merge these two variable into one so I can filter data from that merged variable. So my code is like that
$data1=Model::query()
->Join('...')
->leftJoin('...')
->where('id',login_user)
->select(...)
->whereRaw('id IN (select MAX(id) FROM table GROUP BY name)')
->groupBy('name')
->get();
$data2=Model2::query()
->leftJoin(...)
->select(...)
->where('id',login_user)
->whereNotIn(..)
->get();
both data1 and data2 return same column with different values so I merge both variable like that
$results = $data1->concat($data2);
No when I already get data so now I need to add filter data from $results so i make post method for that .
so When user request to filter data with name I write query like that
if ($request->name!="") {
$results->when(request('name'), function($q){
$q->Where('name', request('name'));
});
}
$records = $results;
return response()->json(['success'=>true,'message'=>'success', 'data' => $records]);
But that query is not filtering the data and return me all data.I am new in laravel so I don't know what I have done wrong in that any favour will be helpful for me ,thanks.
if (request()->has('name')) {
$results->when(request()->get('name'), function($q){
return $q->where('name', request()->get('name'));
});
}
$records = $results;
return response()->json(['success'=>true,'message'=>'success', 'data' => $records]);
As you use 'when()', you can drop the if expression all together:
$results->when(request()->has('name'), function($q){
return $q->where('name', request()->get('name'));
});
$records = $results;
return response()->json(['success'=>true,'message'=>'success', 'data' => $records]);
request() is a Laravel helper for Request $request
Edit: the where() clause in the ORM is with a small 'w', not 'W' as in orWhere

Eloquent: Select field from whereHas block fails

I have got a slightly complex SQL query using a combination of where, whereHas, orWhereHas etc.
Everything goes well but when I add 'custom_records.custom_title' (see below) into the Select fields it fails with:
The Response content must be a string or object implementing __toString(), "boolean" given.
Any ideas?
Here it's the snippet:
`
$record = $this->record->newQuery();`
$record->whereHas('customRecords', function ($query) use ($searchTerm) {
$query->where('custom_title', 'like', '%'.$searchTerm.'%');
});
return $record->get([
'records.id',
'records.another_field',
'records.another_field_2',
'custom_records.custom_title',
]);
Update
When I run the produced SQL query on a mysql client it comes back with:
Unknown column 'custom_records.custom_title',' in 'field list'
You can't select custom_records.custom_title like that. Since it's a HasMany relationship, there can be multiple custom_records per record.
You have to do something like this:
$callback = function ($query) use ($searchTerm) {
$query->where('custom_title', 'like', '%'.$searchTerm.'%');
};
Record::whereHas('customRecords', $callback)
->with(['customRecords' => $callback])
->get(['id', 'another_field', 'another_field_2']);

Laravel - Eloquent - Filter based on latest HasMany relation

I have this two models, Leads and Status.
class Lead extends Model
{
public function statuses() {
return $this->hasMany('App\LeadStatus', 'lead_id', 'id')
->orderBy('created_at', 'DESC');
}
public function activeStatus() {
return $this->hasOne('App\LeadStatus', 'lead_id', 'id')
->latest();
}
}
class LeadStatus extends Model
{
protected $fillable = ['status', 'lead_id'];
}
This works fine, now I'm trying to get all Leads based on the 'status' of the last LeadStatus.
I've tried a few combinations with no success.
if ($search['status']) {
$builder = $builder
->whereHas('statuses', function($q) use ($search){
$q = $q->latest()->limit(1);
$q->where('status', $search['status']);
});
}
if ($search['status']) {
$builder = $builder
->whereHas('status', function($q) use ($search){
$q = $q->latest()->Where('status', $search['status']);
});
}
Has anybody done this with Eloquent? Do I need to write some raw SQL queries?
EDIT 1: I'll try to explain again :D
In my database, the status of a lead is not a 1 to 1 relation. That is because I want to have a historic list of all the statuses which a Lead has had.
That means that when a Lead is created, the first LeadStatus is created with the status of 'new' and the current date.
If a salesman comes in, he can change the status of the lead, but this DOES NOT update the previous LeadStatus, instead it creates a new related LeadStatus with the current date and status of 'open'.
This way I can see that a Lead was created on 05/05/2018 and that it changed to the status 'open' on 07/05/2018.
Now I'm trying to write a query using eloquent, which only takes in count the LATEST status related to a Lead.
In the previous example, if I filter by Lead with status 'new', this Lead should not appear as it has a status of 'open' by now.
Hope this helps
Try this:
Lead::select('leads.*')
->join('lead_statuses', 'leads.id', 'lead_statuses.lead_id')
->where('lead_statuses.status', $search['status'])
->where('created_at', function($query) {
$query->selectRaw('max(created_at)')
->from('lead_statuses')
->whereColumn('lead_id', 'leads.id');
})->get();
A solution using the primary key (by Borjante):
$builder->where('lead_statuses.id', function($query) {
$query->select('id')
->from('lead_statuses')
->whereColumn('lead_id', 'leads.id')
->orderBy('created_at', 'desc')
->limit(1);
});
I had this same problem and posted my solution here but I think it's worth re-posting as it improves on the re-usability. It's the same idea as the accepted answer but avoids using joins, which can cause issues if you want to eager load relations or use it in a scope.
The first step involves adding a macro to the query Builder in the AppServiceProvider.
use Illuminate\Database\Query\Builder;
Builder::macro('whereLatestRelation', function ($table, $parentRelatedColumn)
{
return $this->where($table . '.id', function ($sub) use ($table, $parentRelatedColumn) {
$sub->select('id')
->from($table . ' AS other')
->whereColumn('other.' . $parentRelatedColumn, $table . '.' . $parentRelatedColumn)
->latest()
->take(1);
});
});
This basically makes the sub-query part of the accepted answer more generic, allowing you to specify the join table and the column they join on. It also uses the latest() function to avoid referencing the created_at column directly. It assumes the other column is an 'id' column, so it can be improved further. To use this you'd then be able to do:
$status = $search['status'];
Lead::whereHas('statuses', function ($q) use ($status) {
$q->where('status', $userId)
->whereLatestRelation((new LeadStatus)->getTable(), 'lead_id');
});
It's the same logic as the accepted answer, but a bit easier to re-use. It will, however, be a little slower, but that should be worth the re-usability.
If I understand it correctly you need / want to get all Leads with a specific status.
So you probably should do something like this:
// In your Modal
public function getLeadById($statusId)
{
return Lead::where('status', $statusId)->get();
// you could of course extend this and do something like this:
// return Lead::where('status', $statusId)->limit()....->get();
}
Basically I am doing a where and returning every lead with a specific id.
You can then use this function in your controller like this:
Lead::getLeadById(1)

mySQL query for building dynamically populated model

I have some code below which demonstrates a hard-coded example of what I would like to accomplish dynamically.
At a high level, I wish to do something like select * from view_data_$app_state and then get all of the data from that views table into my mustache templates dynamically.
The code I currently must use to group multiple rows of data for a specific column along with the views data is:
<?php
error_reporting(E_ALL);
class Example {
function __construct(){
try {
$this->db = new PDO('mysql:host=localhost;dbname=Example', 'root','drowssap');
}
catch (PDOException $e) {
print($e->getMessage());
die();
}
}
function __destruct(){
$this->db = null;
}
function string_to_array($links_string){
return explode(",", $links_string);
}
function get_view_data(){
$q = $this->db->prepare('select *, GROUP_CONCAT(`links`) as "links" from `view_data_global` ');
$q->execute();
$result = $q->fetchAll(PDO::FETCH_ASSOC);
return $result;
}
}
$Example = new Example();
$result = $Example->get_view_data();
$result[0]["links"] = $Example->string_to_array($result[0]["links"]);
echo json_encode($result);
This gives me the perfect object while
GROUP_CONCAT seems to be doing the trick this way, however I MUST know the column name that will contain multiple rows before writing the query. I am trying to figure out an approach for this and wish to make a custom query + code example that will transform cols with multiple rows of null null and not empty data into an array like above - but return the data.. again like the code above.
Below is an output of the actual data:
[{"id":"1","title":"This is the title test","links":["main","about","store"]}];
How can I replicate this process dynamically on each view table?
Thank you so much SO!
You can use PDOStatement::fetch to retrieve your results, with fetch_style set to PDO::FETCH_ASSOC (some other values will also provide the same information). In this case, the result set will be array indexed by column name. You can access this information with foreach (array_expression as $key => $value)
See the documentation for additional information.