Splitting a big nested query in Laravel - php

I have a big query which has a nested query. I want to know how to write it so that it's not cluttered. What I tried was creating query objects
//get the latest joined employee per department
$q1 = Employee::where('job', 'assistant')
->groupBy('dept_id')
->select(DB::raw('MAX(empid) as empid'));
//fetch his course ID
$q2 = Employee::whereIn('empid', function($query){
$query->$q1;
})
->where('university', 'LIKE', '%UCLA%')
->select('course_id')
->distinct()
->get()->lists('course_id');
I am getting this error
[Symfony\Component\Debug\Exception\FatalThrowableError]
Cannot access empty property
How should I do it?

You need to inherit your variable from your parent scope in your anonymous function. It's possible with use, like so:
function($query) use ($q1) {
// use $q1 here
}
Read about use (Example #3 Inheriting variables from the parent scope) here: http://www.php.net/manual/en/functions.anonymous.php

You should do it in this way:
$q1 = Employee::where('job', 'assistant')
->groupBy('dept_id')
->select(DB::raw('MAX(empid) as empid'));
$q2 = Employee::whereIn('empid', function($query) use($q1) {
$query->where('id', $q1); // <---- Do it like this
})
->where('university', 'LIKE', '%UCLA%')
->select('course_id')
->distinct();
Hope this helps!

Related

Laravel 5.2 Many to Many query relationship with where clause

I have a little problem, I have a table with Movies and another with Genres ex: Action, Adventure etc...
Table names:
movies
movie_genre
genres
I want to fetch all the Movies with the selected Genre ex: Action
$movies = Movie::with('genres')
->when($genre, function ($query) use ($genre) {
return $query->where('genres.id', $genre->id);
})
->paginate(20);
This php code doesn't work, how can I make it work?
PS: The genre is transfered from the view by $_GET['genre'] and stocked in the variable $genre.
Edit after the answer of #Shane and #Marcin NabiaƂek :
The relationship is set in the models and when I use your code directly like this:
Movie::with('genres', 'countries', 'type')
->when($type, function ($query) use ($type) {
return $query->where('medias.type_id', $type->id);
})
->whereHas('genres', function ($query) use ($genre) {
return $query->where('genres.id', $genre->id);
})
->paginate(20);
it work perfectly, but with the when() function it dosn't. I have to use the when() to make the condition work only if there is a genre selected by the user.
You should use whereHas to get all movies that are assigned to given genre:
$movies = Movie::with('genres')
->when($genre, function ($query) use ($genre) {
$query->whereHas('genres', function($q) use ($genre) {
return $query->where('id', $genre->id);
});
})->paginate(20);
In case $genre is only id and not model, you should rather use:
return $query->where('id', $genre);
and in case it's an array (user is allowed to choose multiple genres), you should use:
return $query->whereIn('id', (array) $genre);
I'm not familiar with the when function. I use whereHas:
$movies = Movie::whereHas('genres', function ($query) use ($genre) {
$query->where('id', $genre->id);
})->with('genres')->get();
Also, is $genre the model or the ID? You mentioned it was received from $_GET which would mean it would be an ID probably.
I think a better solution would be to use your relationship function in the Genre model to get them:
$movies = Genre::find($genre_id)->movies;

How to fetch users not assigned to a particular role in Laravel [duplicate]

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();

Sorting users through relation in laravel

i want to sort the users through voornaam(firstname). but im getting the data via a relation.
How do i make my query so that, the relation users are sorted by firstname by alphabet
my function:
public function sortfirstname($id) {
$ingeschrevenspelers = UserToernooi::with('users')->where('toernooiid', '=', $id)->get()->all();
//This query ^^
$toernooi = Toernooi::findOrFail($id);
dd($ingeschrevenspelers);
return view('adminfeatures.generatespelerslijst', compact('ingeschrevenspelers', 'toernooi'));
}
What i want to sort
any help is appreciated
thanks in advance
Writing code in your own language doesn't make it very easy for other developers to understand your code.
That being said, you can try the orderBy() method on your relationship
In your model where you define the relationship:
public function relationship()
{
return $this->belongsTo(SomeClass::class)->orderBy('name', 'DESC');
}
Don't fire all() function at the end thus obtaining a Collection instance of result
//query without the all function
$ingeschrevenspelers = UserToernooi::with('users')->where('toernooiid', '=', $id)->get();
//
$ingeschrevenspelers = $ingeschrevenspelers->sortBy('users.firstname');
An alternative to Jordy Groote's answer if you do not want to modify the Model class itself, you can query it with a closure.
$ingeschrevenspelers = UserToernooi::with(['users' => function($q) {
$q->orderBy('voornaam', 'asc');
}])->where('toernooiid', '=', $id)->get()->all();
Reference: https://laravel.com/docs/5.3/eloquent-relationships#constraining-eager-loads
Sidenote: I don't think you need a ->all() when you already did a ->get()
$ingeschrevenspelers = UserToernooi::with(['users' => function($query){
$query->orderBy('voornaam', 'asc');
}])->where('toernooiid', '=', $id)->get()->all();

Laravel fetch data from model where the condtions is from another model

I have a table called List which i planned to be displayed into view with this command : $lists= List::with('user', 'product.photodb', 'tagCloud.tagDetail')->get();. But, i want the data displayed is only those that has TagID equal to the one user inputted. Those data can be retrieved from TagCloud table.
What i am currently doing is :
$clouds = TagCloud::select('contentID')
->where('tagDetailID', '=', $tagID)
->get();
$lists = List::with('user', 'product.photodb', 'tagCloud.tagDetail')
->where('id', '=', $clouds->contentID)
->get();
But when i tried to run it, it only return a null value, even though when i am doing return $clouds, it does returned the desired ID.
Where did i do wrong ? Any help is appreciated !
A couple of gotchas with your current solution.
Using get() returns an Illuminate\Database\Eloquent\Collection object. Hence you can't use $clouds->contentID directly since $clouds is a collection (or array if you prefer). See Collection Documentation.
where(...) expects the third parameter to be a string or integer, aka single value. Instead, you are passing a collection, which won't work.
The correct way is to use whereHas() which allows you to filter through an eager loaded relationship.
Final Code:
$lists = List::with('user', 'product.photodb', 'tagCloud.tagDetail')
->whereHas('tagCloud',function($query) use ($tagID) {
return $query->where('contentID','=',$tagID);
})
->get();
See WhereHas Documentation.
What you want is whereHas()
$list = List::with(...)
->whereHas('relation', function($q) use($id) {
return $q->where('id', $id);
})->get();
Apply Where condition in you tagCloud model method tagDetail
public function tagDetail(){
return $q->where('id', $id);
}

More than one way to invert an Eloquent query?

Consider the following query:
$tickets = Account::with('tickets')->where('name','LIKE',"%{$search}%")->paginate(10);
This is how I inverted it:
$tickets = Ticket::whereHas('account', function($q) use ($search)
{
$q->where('name', 'LIKE', '%'. $search .'%');
})->paginate(10);
Is there another way to invert the first query?
I was looking for a way to execute the first query and receive only results from the tickets table. As it is now of course I will receive the tickets as a property of each account returned.
Answering your comment: yes, you can fetch tickets from 1st query. This is how to do it, though pagination is run in the context of accounts, so we will skip it and retrieve all the accounts:
// returns Eloquent Collection of Account models
$accounts = Account::with('tickets')->where('name','LIKE',"%{$search}%")->get();
// returns array of Ticket arrays (not models here)
$tickets = $accounts->fetch('tickets')->collapse();
As you can see it's possible but a bit cumbersome. Of course you can always prepare a helper method for this, but still you get arrays instead of models etc
So in fact I suggest using the 2nd solution, but here I'll give you something to make it nice and easy:
// Ticket model
public function scopeHasCategoryNameLike($query, $search)
{
$query->whereHas('categories', function($q) use ($search) {
$q->where('name', 'like', "%{$search}%");
});
}
Then it's as simple as this:
$tickets = Ticket::hasCategoryNameLike($search)->paginate(10);

Categories