eloquent or-query over pivot table - php

i have this 2 models
Track
id
name
artist_id
Artist
id
name
class Song extends \Eloquent{
public function artist(){
return $this->hasOne('Artist','id', 'artist_id');
}
}
class Artist extends \Eloquent{
public function songs(){
return $this->hasMany('Song', 'artist_id', 'id');
}
}
Now i want to realize a search given the search term "happy pharrel".
It is clear to me that i have to search the songs where the name of the Song is "happy" or the name of the artist is "happy" and where the name of the song is "pharrel" or the name of the artist is "pharrel".
now i'm wondering how i could do such a query with a condition to the song table and the artist table?

Your code shows that you have specified relations between your tables / classes -- this is good, but I don't think it necessarily helps in the case of a search. It's an excellent way to keep your syntax clean and also for eager loading, but you'll be better off writing a new query for search terms which need to search different columns on different tables.
I'm thinking something along these lines, inside a controller method called when a user submits his search:
$keywords = explode(' ', Input::get('search'));
$results = Song::join('artists', 'songs.artist_id', '=', 'artists.id')
->whereIn('artists.name', $keywords)
->orWhereIn('songs.name', $keywords)
->get();
It's fairly simplistic, but should be enough to get you started.

as tbuteler answered it's the quickest way to get the results to to a database query.
foreach($keywords as $p){
$songs->where(function($query) use($p){
$query->where('artists.name',"like", "%".$p."%")
->orWhere('songs.name', "like", "%".$p."%");
});
}
if you dont do the where(function($query){...} there is 1 query with many or's but and connection between each "or" part is missing
maybe that helps somebody else later

Related

Laravel, search exact same substring in eloquent query

I have a table like this
Teacher Table
What I am trying to do is to get the row which contains the subjects 1(or any other number like 7,8 etc.)
This is what I have tried in my controller.
public function allTeachers($sub_id) //receiving $sub_id(to be searched)
{
$teachers_all=Teacher::where('subjects','like','%'.','.$sub_id.'%')->latest()->paginate(50);
dd($teachers_all);
}
The problem here is that, I am getting all the rows which contains subjects as '1',e.g. if it is '3,11,22' or '41,5' it gets selected.
But what I am trying to achieve is it should only return where subjects string contains '1' followed by any other number after ',' or '1,2,44,31,23' etc.
I am using laravel, I hope I made the question clear.
The solution to your question would be either to use find_in_set or concat to fill some missing commas and then search for the value:
Teacher::whereRaw('find_in_set("' . $sub_id . '", subjects) <> 0')
->latest()
->paginate(50);
or
Teacher::whereRaw('concat(",", colors, ",") like "%,' . $sub_id . ',%"')
->latest()
->paginate(50);
That being said, #bromeer's comments hold true in any case. MySQL isn't around comma-separated values in fields. Both examples shown above aren't an ideal solution. You should look into relationships a bit more.
I suggest using a many-to-many relationship in your case. For that, create a pivot table called teacher_subject and add the relation to your Teacher model:
public function subjects()
{
return $this->belongsToMany(Subject::class);
}
To find any teachers teaching a specific subject, use whereHas like this:
Teacher::whereHas('subjects', function (Builder $query) use ($sub_id) {
$query->where('id', $sub_id);
})->latest()->paginate(50);

How To Use spatie/laravel-query-builder For Filtering/Searching Data with Relationship? (Laravel)

Someone recommended me using spatie/laravel-query-builder for filtering/searching data when I ask about how to filter data that had relationship. I'm still confused by it.
What I'm trying to do: filter/search data with roles of 'Student'(relationship) and contain the 'x' word.
mycontroller.php before adding any spatie/query-builder code
public function searchStudent(Request $request)
{
$user = Auth::user(); // Untuk Photo Profile
// menangkap data pencarian
$search = $request->table_search;
// This part is the code that supposed to filter the search that has relation to data named Student in name column
$search = User::whereHas('roles', function($q){
$q->where('name', 'Student');
})->where('name','like',"%".$search."%")
->orWhere('nisn','like',"%".$search."%")
->orWhere('username','like',"%".$search."%")
->get();
// // what I got is actually the normal search, so all other relation than Student also show up
return view('pages.admin.user.student.showStudentFiltered', compact('search', 'user') );
}
What I got is just an old regular search... They just show all data that contain 'x' word but not the relationship they had.
Example Search Data I wanted: Search Words 'A' users that had 'Student' roles
Aqua|roles: Student
Armin|roles: Student
Alpine|roles: Student
What I got:
Admin|roles: Admin
Amsterdam|roles: Teacher
Amel|roles: Teacher
Aqua|roles: Student
Armin|roles: Student
Alpine|roles: Student
Anyone know how to use spatie/laravel-query-builder or how to got filter/search function like what I wanted? What code I need to rewrite?
The query builder whereHas's underlying SQL doesn't join, it's just an extra where clause. with will join for you.
Use
User::with('roles')->whereHas('roles', function($q){
$q->where('name', 'Student');
})

Return belonging name with ID form Laravel, check for the type?

sorry for the title of this question but I am not sure how to ask it...
I am working on a project where I have two Models Trains and Cars, to this model I have a belonging Route.
I want to make a query and check if the routeable_type is App\Car than with the selected routeable_id to get the data from the Car. And if the routeable_type is Train then with the ID to get the data from the Tran.
So my models go like this:
Train:
class Train extends Model
{
public function routes()
{
return $this->morphMany('App\Route', 'routeable');
}
}
Car:
class Car extends Model
{
public function routes()
{
return $this->morphMany('App\Route', 'routeable');
}
}
Route:
class Route extends Model
{
public function routeable()
{
return $this->morphTo();
}
}
And the query I have at the moment is:
$data = Route::leftjoin('cars', 'cars.id', '=', 'routes.routeable_id')
->leftjoin('trains', 'trains.id', '=', 'routes.routeable_id')
->select('routes.id', 'cars.model AS carmodel', 'trains.model AS trainmodel', 'routeable_type', 'routes.created_at');
With this query if I have the same ID in cars and trains I get the data from both and all messes up. How do I check if routeable_type is Car ... do this, if routeable_type is Train .. do that?
Will something like this be possible in a 1 single query:
$data = Route::select('routes.id', 'routeable_type', 'routes.created_at');
if(routeable_type == 'Car'){
$data = $data->leftjoin('cars', 'cars.id', '=', 'routes.routeable_id')->select('routes.id', 'cars.model AS carmodel', 'routeable_type', 'routes.created_at');
}else{
$data = $data->leftjoin('trains', 'trains.id', '=', 'routes.routeable_id')->select('routes.id', 'trains.model AS trainmodel', 'routeable_type', 'routes.created_at');
}
Maybe this is what you are looking for?
DB::table('routes')
->leftJoin('cars', function ($join) {
$join->on('cars.id', '=', 'routes.routeable_id')
->where('routes.routeable_type', 'App\Car');
})
->leftJoin('trains', function ($join) {
$join->on('trains.id', '=', 'routes.routeable_id')
->where('routes.routeable_type', 'App\Train');
})
->select('routes.id', 'cars.model AS car_model', 'trains.model AS train_model', 'routes.routeable_type', 'routes.created_at');
->get();
I think you may want to follow the morphedByMany design.
https://laravel.com/docs/5.7/eloquent-relationships#many-to-many-polymorphic-relations
This was also a neat visual for the different relation types.
https://hackernoon.com/eloquent-relationships-cheat-sheet-5155498c209
I was faced with a similar issue though I failed to follow the correct design initially and was forced to query the many possible relations then wrote custom logic after to collect the relation types and ids then do another query and assign them back through iteration. It was ugly but worked... very similar to how Eloquent does things normally.
i don't have enough repo, so i can't comment. that's why i am putting as an answer.
You should use 2 different queries, for each model.
This will be better, code wise as well as performance wise. also if both models have similar fields you should merge them to 1 table and add a 'type' column.
and put non-similar fields in a 'meta' column.
( in my opinion )

filtering result using eager loading

I am creating a search in laravel for an API but my search gives me the wrong results. I am trying to search by location and food type. I have the following tables:
foods
shops
shop_food
users
comments
Here is my search code:
public function searchShop($food, $location)
{
//
if($food == " " || $location == " "){
return $this->index();
}
//get all records where city and food are equal to
$shops = Shop::where('city', '=', $location)
->with('comments.user')
->with(['foods'=> function($query) use($food){
$query->where('name','=', 'fish pepper'); }])
->get();
//check if empty and return all
if($shops->isEmpty()){
return $this->index();
}
return $shops;
}
my result is the below instead of just the record where location and food it shows all the shops filtered by location even where food isnt a match :
The with method that you're using does not filter in the way that you think it does. Your code actually filters the food results, telling Eloquent to retrieve all Shop and either no foods, or the foods with the name fish pepper. This is called constraining eager loads.
The method you're looking for is whereHas rather than with. This is referred to as querying a relationships existence.
$shops = Shop::where('city', '=', $location)
->with('comments.user')
->whereHas('foods', function($query) use($food){
$query->where('name','=', 'fish pepper');
})
->get();
This will now return only Shops that have a corresponding food entry with the name fish pepper.
If memory serves, whereHas won't actually populate foods for you, but in this instance you wouldn't need it, as it's safe to assume that they all have fish pepper. If you do want to pull all foods, change with('comments.user') to with(['comments.user', 'foods']).
Documentation for whereHas and other ways of achieving this can be found here.
Documentation about what you were doing with the with method can be found here.
Hope that helps.

Eloquent HasMany relationship, with limited record count

I want to limit related records from
$categories = Category::with('exams')->get();
this will get me exams from all categories but what i would like is to get 5 exams from one category and for each category.
Category Model
public function Exams() {
return $this->hasMany('Exam');
}
Exam Model
public function category () {
return $this->belongsTo('Category');
}
I have tried couple of things but couldnt get it to work
First what i found is something like this
$categories = Category::with(['exams' => function($exams){
$exams->limit(5);
}])->get();
But the problem with this is it will only get me 5 records from all categories. Also i have tried to add limit to Category model
public function Exams() {
return $this->hasMany('Exam')->limit(5);
}
But this doesnt do anything and returns as tough it didnt have limit 5.
So is there a way i could do this with Eloquent or should i simply load everything (would like to pass on that) and use break with foreach?
There is no way to do this using Eloquent's eager loading. The options you have are:
Fetch categories with all examps and take only 5 exams for each of them:
$categories = Category::with('exams')->get()->map(function($category) {
$category->exams = $category->exams->take(5);
return $category;
});
It should be ok, as long as you do not have too much exam data in your database - "too much" will vary between projects, just best try and see if it's fast enough for you.
Fetch only categories and then fetch 5 exams for each of them with $category->exams. This will result in more queries being executed - one additional query per fetched category.
I just insert small logic inside it which is working for me.
$categories = Category::with('exams');
Step 1: I count the records which are coming in response
$totalRecordCount = $categories->count()
Step 2: Pass total count inside the with function
$categories->with([
'exams' => function($query) use($totalRecordCount){
$query->take(5*$totalRecordCount);
}
])
Step 3: Now you can retrieve the result as per requirement
$categories->get();

Categories