Eloquent: Select field from whereHas block fails - mysql

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']);

Related

Need suggestion in query builder of Laravel 9 application

How to avoid foreach-loop because Laravel query builder is used to get a single record detail.
When I tried without foreach-loop, the following error occurred "Property [name] does not exist on this collection instance".
Controller
public function show(Student $student)
{
$result = DB::table('students')
->select('*')
->where('id', "=" ,$student['id'])
->get();
foreach($result as $studentData){}
return view('student.show', ['studentData' => $studentData]);
}
Blade View
{{ $studentData->name}}
->get() always returns a Collection, even if there's only one match. If you only want the first record, you can use ->first() instead.
Do you really have to use raw queries? I assume Student is a Laravel Model, in that case, you should be able to use Student::find($id) to get the model directly.

Eloquent relation query not functioning

I'm scratching my head over this database query and couldn't find out why it is not working.
It actually works if I try it directly on the plays table :
Play::select(\DB::raw('COUNT(*) as plays'), \DB::raw('DATE(created_at) as date'))
->whereDate('created_at', '>', Carbon::now()->subWeek())
->groupBy('date')->orderBy('date')->get();
// returns the expected results
However, it does not work when I try to use it inside relation function ( returns empty array, while there is actually data ):
$artist->songs()->with(['plays' => function($q){
$q->select(\DB::raw('COUNT(*) as plays'), \DB::raw('DATE(created_at) as date'))
->whereDate('created_at', '>', Carbon::now()->subWeek())
->groupBy('date')->orderBy('date');
}])->get()->pluck('plays')->toArray();
NB: $artist->songs()->with('plays')->get() return the expected results
I guess you need to send id inside select statement.
$artist->songs()->with(['plays' => function($q){
$q->select(\DB::raw('COUNT(*) as plays'), \DB::raw('DATE(created_at) as date'), 'id')
->whereDate('created_at', '>', Carbon::now()->subWeek())
->groupBy('date')->orderBy('date');
}])->get()->pluck('plays')->toArray();

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)

Retrieve conditions matched on laravel model query

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!

Laravel Fluent queries - How do I perform a 'SELECT AS' using Fluent?

I have a query to select all the rows from the hire table and display them in a random order.
DB::table('hire_bikes')->order_by(\DB::raw('RAND()'))->get();
I now want to be able to put
concat(SUBSTRING_INDEX(description, " ",25), "...") AS description
into the SELECT part of the query, so that I can select * from the table and a shortened description.
I know this is possible by running a raw query, but I was hoping to be able to do this using Fluent or at least partial Fluent (like above).
How can I do it?
You can actually use select AS without using DB::raw(). Just pass in an array into the select() method like so:
$event = Events::select(['name AS title', 'description AS content'])->first();
// Or just pass multiple parameters
$event = Events::select('name AS title', 'description AS Content');
$event->title;
$event->content;
I tested it.
Also, I'd suggest against using a DB:raw() query to perform a concatenation of your description field. If you're using an eloquent model, you can use accessors and mutators to perform this for you so if you ever need a limited description, you can simply output it in your view and not have to use the same query every time to get a limited description. For example:
class Book extends Eloquent
{
public function getLimitedDescriptionAttribute()
{
return str_limit($this->attributes['description'], $limit = 100, $end = '...');
}
}
In your view:
#foreach($books as $book)
{{ $book->limited_description }}
#endforeach
Example Output (not accurate to limit):
The description of this book is...
I'd also advise against using the DB facade because it always utilizes your default connection. If you're querying a secondary connection, it won't take this into account unless you actively specify it using:
DB::connection('secondary')->table('hire_bikes')->select(['name as title'])->get();
Just to note, if you use a select AS (name AS title) and you wish to update your the model, you will still have to set the proper attribute name that coincides with your database column.
For example, this will cause an exception because the title column does not exist in your database table:
$event = Events::select('name AS title')->first();
$event->title = 'New name';
$event->save(); // Generates exception, 'title' column does not exist.
You can do this by adding a DB::raw() to a select an array in your fluent query. I tested this locally and it works fine.
DB::table('hire_bikes')
->select(
array(
'title',
'url',
'image',
DB::raw('concat(SUBSTRING_INDEX(description, " ",25),"...") AS description'),
'category'
)
)
->order_by(\DB::raw('RAND()'))
->get();
select(array(DB::raw('latitude as lat'), DB::raw('longitude as lon')))