I have a user table that has related data on a belongsToMany basis
users
id
first_name
skills
id
name
positions
id
name
position_user
position_id
user_id
created_at
updated_at
skill_user
skill_id
user_id
created_at
updated_at
In User model
public function positions()
{
return $this->belongsToMany('App\Position')->withTimestamps();
}
and in Position
public function users()
{
return $this->belongsToMany('App\User')->withTimestamps();
}
(the same for skills)
I am currently passing the following to a view:
$users = User::with('skills')
->with('skills')
->with('positions')
->get();
I want to be able to search on various combinations of skills and positions but am having difficulty creating an elegant solution.
If I select a position or positions, pass that to the controller I can return info as:
if (Request::get('positions'))
{
$positions = Request::get('positions');
}
Where the positions array could look like
array:3 [?
0 => "Analyst"
1 => "Attorney"
2 => "Architect"]
if($positions)
{
$users = User::with('skills')
->with('skills')
->with('positions')
->orWhereHas('positions', function($thisquery) use ($positions)
{
$thisquery->whereIn('name', $positions);
})
->get();
}
If I do the same with skills that works as well.
What I need to do is to combine them - especially since I will be adding more related tables into the search function.
I have tried:
if(($positions)&&($skills))
{
$users = User::with('skills')
->with('skills')
->with('positions')
->orWhereHas('positions', function($thisquery) use ($positions)
{
$thisquery->whereIn('name', $positions);
})
->orWhereHas('skills', function($thisquery) use ($skills)
{
$thisquery->whereIn('name', $skills);
})
->get();
}
But what I am wanting is something more like
$users = User::with('skills')
->with('skills')
->with('positions');
if($skills)
{
$users->orWhereHas('skills', function($thisquery) use ($skills)
{
$thisquery->whereIn('name', $skills);
});
}
if($positions)
{
$users->orWhereHas('positions', function($thisquery) use ($positions)
{
$thisquery->whereIn('name', $positions);
});
}
$users->get();
However that doesn't work -just returns empty resultset.
How can I achieve this in an elegant way?
Or is there perhaps a better way to implement a search function - really I am just wanting to filter on certain parameters.
I think you need "and where" condition here. Or where means matching either one or another or both rules. "And where" would only work if both rules match.
So I'd suggest trying this:
$users = User::with('skills')
->with('skills')
->with('positions');
if($skills)
{
$users->whereHas('skills', function($thisquery) use ($skills)
{
$thisquery->whereIn('name', $skills);
});
}
if($positions)
{
$users->whereHas('positions', function($thisquery) use ($positions)
{
$thisquery->whereIn('name', $positions);
});
}
$users = $users->get();
I solved this problem myself recently.
What I ended up doing was the following:
Get all relevant data into an array.
Filter that array to match parameter values
Return the filtered array.
You can even cache step 1 and recache when you add or update an element.
Go ahead and try an implementation. If you should get stuck, please update your question with relevant code.
Related
I am trying to get array of related model to my data and it returns null.
Code
public function collection()
{
return Product::with(['allBarcodes' => function ($query) {
$query->select('serial_number');
}])->get();
}
result
Also I tried pluck like $query->pluck('serial_number'); and result was
My real data
the data I suppose to receive is like
[{
"id":1,
"product_id":1,
"serial_number":"5245412185", // I only need this to be return as array
"sold":1,
"created_at":"2020-05-24T04:21:56.000000Z",
"updated_at":"2020-05-24T04:21:56.000000Z"
}]
Any idea?
When you are doing this $query->select('serial_number'); you are only selecting serial_number and not the column that connects both the modals i.e. product_id inside barcodes table.
Do this.
$query->select('product_id', 'serial_number');. However this will return 2 columns. If you want just one then you will have to use collection transform.
$products = $products->map(function ($product) {
$product->allBarcodes->transform(function ($q) {
return $q->serial_number;
});
return $product;
});
Keep me posted in the comments below.
I have an application where I want to fetch parent records based on children conditionals. Current problem is that I have Students, where they have multiple study fields and study fields belong to one faculty. Pivot table students_study_fields has attribute study_status_id.
What I need is, for example, fetch all students and their study fields which belongs to "prf" faculty AND pivot has study_status_id = 1.
So I write a query like this.
return Student::with(['studyfields' => function ($query1) use ($studyStatusId, $facultyAbbreviation) {
$query1->whereHas('pivot', function ($query2) use ($studyStatusId, $facultyAbbreviation) {
$query2->where('study_status_id', $studyStatusId);
});
$query1->whereHas('studyprogram', function ($query4) use ($facultyAbbreviation) {
$query4->whereHas('faculty', function ($query5) use ($facultyAbbreviation) {
$query5->where('abbreviation', $facultyAbbreviation);
});
});
}])->get();
But this query fetch students witch study_status_id = 2 as well because exists record where this same study field (its code) has relation with student, where study_status_id = 1.
So I don't want to include this studyfield if somewhere exists record with status = 1 in pivot but only if has status = 1 for current row
You need to chain the queries...
return Student::with(['studyfields' => function ($query1) use ($studyStatusId, $facultyAbbreviation) {
$query1->whereHas('pivot', function ($query2) use ($studyStatusId, $facultyAbbreviation) {
$query2->where('study_status_id', $studyStatusId);
})->whereHas('studyprogram', function ($query4) use ($facultyAbbreviation) {
$query4->whereHas('faculty', function ($query5) use ($facultyAbbreviation) {
$query5->where('abbreviation', $facultyAbbreviation);
});
});
}])->get();
Otherwise it will re-start the query1 so you won't get AND kind of query, only get the second part
Side Note: However, I want to warn you that whereHas is a slow query if you have many rows as it goes through each value. I personally prefer grabbing the ids with simple ->where queries and utilise ->whereIn approach.
I found solution for my situation
$students = Student::with(['studyfields' => function ($q) use ($studyStatusId) {
$q->whereHas('pivot')->where('study_status_id', $studyStatusId);
}])
->whereHas('studyfields', function ($q) use ($facultyAbbreviation) {
$q->whereHas('studyprogram', function ($q) use ($facultyAbbreviation) {
$q->where('faculty_abbreviation', $facultyAbbreviation);
});
})
->get();
$students = $students->filter(function ($student) {
return count($student->studyfields) > 0;
})->values();
Query above fetch all students from specific faculty and if studyfields array doesn't contains specific study_status, leave empty array so later I can filter collection from empty arrays assuming that each student belongs to at least one studyfield.
I have tables Polfzms <- Genes
Polfzm model have next relation
public function gene()
{
return $this->belongsTo('App\Gene');
}
I need get all data from Polfzms table with data from Genes table and order it by name from pivot table (Genes). I try next
$data = Polfzm::with([
'gene' => function ($query) {
$query->orderBy('name', 'asc');
},
])->get();
but it not order data by name. How can I do it?
You could try to set this in the relationship definition:
Polfzm.php
public function gene()
{
return $this->belongsTo('App\Gene')->orderBy('name', 'asc');
}
Then in your controller:
$data = Polfzm::with('gene')->get();
If I understand correctly, you could use a collection sortBy helper for this one.
An example could be:
$data = Polfzm::with('gene')
->get()
->sortBy(function ($polfzm) {
return $polfzm->gene->name;
});
In Laravel we can setup relationships like so:
class User {
public function items()
{
return $this->belongsToMany('Item');
}
}
Allowing us to to get all items in a pivot table for a user:
Auth::user()->items();
However what if I want to get the opposite of that. And get all items the user DOES NOT have yet. So NOT in the pivot table.
Is there a simple way to do this?
Looking at the source code of the class Illuminate\Database\Eloquent\Builder, we have two methods in Laravel that does this: whereDoesntHave (opposite of whereHas) and doesntHave (opposite of has)
// SELECT * FROM users WHERE ((SELECT count(*) FROM roles WHERE user.role_id = roles.id AND id = 1) < 1) AND ...
User::whereDoesntHave('Role', function ($query) use($id) {
$query->whereId($id);
})
->get();
this works correctly for me!
For simple "Where not exists relationship", use this:
User::doesntHave('Role')->get();
Sorry, do not understand English. I used the google translator.
For simplicity and symmetry you could create a new method in the User model:
// User model
public function availableItems()
{
$ids = \DB::table('item_user')->where('user_id', '=', $this->id)->lists('user_id');
return \Item::whereNotIn('id', $ids)->get();
}
To use call:
Auth::user()->availableItems();
It's not that simple but usually the most efficient way is to use a subquery.
$items = Item::whereNotIn('id', function ($query) use ($user_id)
{
$query->select('item_id')
->table('item_user')
->where('user_id', '=', $user_id);
})
->get();
If this was something I did often I would add it as a scope method to the Item model.
class Item extends Eloquent {
public function scopeWhereNotRelatedToUser($query, $user_id)
{
$query->whereNotIn('id', function ($query) use ($user_id)
{
$query->select('item_id')
->table('item_user')
->where('user_id', '=', $user_id);
});
}
}
Then use that later like this.
$items = Item::whereNotRelatedToUser($user_id)->get();
How about left join?
Assuming the tables are users, items and item_user find all items not associated with the user 123:
DB::table('items')->leftJoin(
'item_user', function ($join) {
$join->on('items.id', '=', 'item_user.item_id')
->where('item_user.user_id', '=', 123);
})
->whereNull('item_user.item_id')
->get();
this should work for you
$someuser = Auth::user();
$someusers_items = $someuser->related()->lists('item_id');
$all_items = Item::all()->lists('id');
$someuser_doesnt_have_items = array_diff($all_items, $someusers_items);
Ended up writing a scope for this like so:
public function scopeAvail($query)
{
return $query->join('item_user', 'items.id', '<>', 'item_user.item_id')->where('item_user.user_id', Auth::user()->id);
}
And then call:
Items::avail()->get();
Works for now, but a bit messy. Would like to see something with a keyword like not:
Auth::user()->itemsNot();
Basically Eloquent is running the above query anyway, except with a = instead of a <>.
Maybe you can use:
DB::table('users')
->whereExists(function($query)
{
$query->select(DB::raw(1))
->from('orders')
->whereRaw('orders.user_id = users.id');
})
->get();
Source: http://laravel.com/docs/4.2/queries#advanced-wheres
This code brings the items that have no relationship with the user.
$items = $this->item->whereDoesntHave('users')->get();
I have a query which looks like this:
$items = Item::live()
->with('location')
->where('last_location_id', Input::get('last_location_id'))
->get();
The background of this is...
2 tables: Items & Cars.
The live scope is:
public function scopeLive($query)
{
return $query->whereHas('basic_car', function($q)
{
$q->whereNotNull('id')->where('sale_status', 'Live');
});
}
This basically checks the cars table for a matching id to that of the items 'car_id' field and will run some where clauses on the cars table.
I now however want to check another field on the cars table, but using the Input::get('last_location_id') from the original query.
$items = Item::live()
->with('location')
->where('last_location_id', Input::get('last_location_id'))
->orWhere('ROW ON THE CARS TABLE' = Input::get('last_location_id'))
->get();
This does't work, then I tried:
$items = Item::live()
->with('location')
->where('last_location_id', Input::get('last_location_id'))
->orWhere(function($query)
{
$query->where('cars.Location', Input::get('last_location_id'));
})
->get();
Which results in an unknown column 'cars.Location' error.
My next test was to create another scope:
public function scopeLiveTest($query)
{
return $query->whereHas('basic_car', function($q)
{
$q->whereNotNull('id')->where('sale_status', 'Live')->where('Location', 1); // hardcoded ID
});
}
And replacing the live() scope with that works but I dont get the affect of the orWhere in the query itself and I also cannot specify a ID from the Input.
How can I do this?
You can pass a parameter to scope like this:
$items = Item::liveAtLocation(Input::get('last_location_id'))
->orWhere(function( $query ) { // to get the OR working
$query->live()
->with('location')
->where('last_location_id', Input::get('last_location_id'));
})
->get();
And for the scope:
public function scopeLiveAtLocation($query, $location_id)
{
return $query->whereHas('basic_car', function($q) use ($location_id)
{
$q->whereNotNull('id')->where('sale_status', 'Live')->where('Location', $location_id);
});
}