Getting all users except admins in many-to-many relationship - mysql

I have a users table and roles table, connecting them in many-to-many relationship in role_user table.
I want to get all users except the users that have admin role, I want to include the users that do not have any roles.
Basically all users except admins.

Expecting that the relationships are setup properly, this can be achieved rather easily with whereDoesntHave():
$roleToExclude = 1;
$users = User::query()
->whereDoesntHave('roles', function (Builder $query) use ($roleToExclude) {
$query->where('id', $roleToExclude);
})
->get();
Regarding the comment: if you want to retrieve all users that have at least one role, but their roles may not contain the admin role, then you can use this query:
$roleToExclude = 1;
$users = User::query()
->has('roles')
->whereDoesntHave('roles', function (Builder $query) use ($roleToExclude) {
$query->where('id', $roleToExclude);
})
->get();
has('roles') will ensure there EXISTS one role for the user, while whereDoesntHave('roles', fn()) will ensure it is not an admin role.
A note about the suggested edit of #Jino Antony:
When dealing with many-to-many relations, all the whereX($col, $val) methods of the query builder operate on the other table (roles in this case), not the pivot table (role_user). To query a column on the pivot table, you'd need to use wherePivot('role_id', $roleToExclude) in my example.

Add a relation in the User model.
User.php
public function roles(){
return $this->belongsToMany(Role::class);
}
For Retrieving
$user = User::whereHas('roles', function($query){
$query->where('name', '<>', 'admin') // role with no admin
});
For plain MYSQL
SELECT u.* FROM users u
INNER JOIN role_user ru ON ru.user_id = u.id
INNER JOIN roles r ON r.id = ru.role_id WHERE r.name <> 'admin';

Since Above Answers are missing the Reverse Method i have added that Relation
public function roles()
{
return $this->belongsToMany(Role::class);
}
//roles that need to be excuded
//it also accepts the array
$rolesExcept = 'admin';
//roles that need to be included
//it also accepts the array
$rolesOnly = 'admin';
//closure that filter the the rolesOnly
$withSpecificRolesClosure = function ($query) use ( $rolesOnly)
{
$query-> whereIn( 'name', (array) $rolesOnly); // role with only admin
};
//closure that filter the the rolesExcept
$withOutSpecificRolesClosure = function ($query) use ( $rolesExcept)
{
$query->whereNotIn('name', (array)$rolesExcept); // role with no admin
};
//get all the users with the role with admim
$userWithRoleAdmin = App\Models\User::whereHas('roles', $withSpecificRolesClosure)->get();
//get all the users with the role without admim
$userWithOutRoleAdmin = App\Models\User::whereHas('roles',$withOutSpecificRolesClosure)->get();

can you try this one. if you're not using a model relationship this would work
$users = DB::table(role_user as ru')
->join('users as u', 'ru.user_id', '=', 'u.id')
->join('roles as r', 'r.id', '=', 'ru.id')
->where('r.name', '<>', 'admin')->get()

try below code:
$users = User::whereDoesntHave('roles', function ($query) {
$query->where('name', Role::ROLE_ADMIN);
})->get();
add relationship code in User.php file.
public function roles()
{
return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
}

Related

Add result of subquery to Eloquent query in Laravel 9

In Laravel 9 I am trying to add the result of a subquery to a query(for lack of better wording) and I am stuck. More concretely, I am trying to load all products and at the same time add information about whether the current user has bought that product.
Why do I want to do this?
I am currently loading all products, then loading all bought products, then comparing the 2 to determine if the user has bought a product, but that means extra queries which I would like to avoid. Pretend for the sake of this question that pagination doesn't exist(because when paginating the impact of those multiple queries is far diminished).
There is a many to many relationship between the 2 tables users and products, so these relationships are defined on the models:
public function products()
{
return $this->belongsToMany(Product::class);
}
and
public function users()
{
return $this->belongsToMany(User::class);
}
What I have tried so far:
I created a model for the join table and tried to use selectRaw to add the extra 'column' I want. This throws a SQL syntax error and I couldn't fix it.
$products = Product::query()
->select('id', 'name')
->selectRaw("ProductUser::where('user_id',$user->id)->where('product_id','products.id')->exists() as is_bought_by_auth_user")
->get();
I tried to use addSelect but that also didn't work.
$products = Product::query()
->select('id', 'name')
->addSelect(['is_bought_by_auth_user' => ProductUser::select('product_id')->where('user_id',$user?->id)->where('product_id','product.id')->first()])
->get();
I don't even need a select, I actually just need ProductUser::where('user_id',$user?->id)->where('product_id','product.id')->exists() but I don't know a method like addSelect for that.
The ProductUser table is defined fine btw, tried ProductUser::where('user_id',$user?->id)->where('product_id','product.id')->exists() with hardcoded product id and that worked as expected.
I tried to create a method on the product model hasBeenBoughtByAuthUser in which I wanted to check if Auth::user() bought the product but Auth wasn't recognized for some reason(and I thought it's not really nice to use Auth in the model anyway so didn't dig super deep with this approach).
$products = Product::query()
->select('id', 'name')
->addSelect(\DB::raw("(EXISTS (SELECT * FROM product_user WHERE product_users.product_id = product.id AND product_users.user_id = " . $user->id . ")) as is_bought_by_auth_user"))
->simplePaginate(40);
For all attempts $user=$request->user().
I don't know if I am missing something easy here but any hints in the right direction would be appreciated(would prefer not to use https://laravel.com/docs/9.x/eloquent-resources but if there is no other option I will try that as well).
Thanks for reading!
This should do,
$id = auth()->user()->id;
$products = Product::select(
'id',
'name',
DB::raw(
'(CASE WHEN EXISTS (
SELECT 1
FROM product_users
WHERE product_users.product_id = products.id
AND product_users.user_id = '.$id.'
) THEN "yes" ELSE "no" END) AS purchased'
)
);
return $products->paginate(10);
the collection will have purchased data which either have yes or no value
EDIT
If you want eloquent way you can try using withExists or withCount
i.e.
withExists the purchased field will have boolean value
$products = Product::select('id', 'name')->withExists(['users as purchased' => function($query) {
$query->where('user_id', auth()->user()->id);
}]);
withCount the purchased field will have count of found relationship rows
$products = Product::select('id', 'name')->withCount(['users as purchased' => function($query) {
$query->where('user_id', auth()->user()->id);
}]);

Laravel: hasMany relationship + where condition fails

I have a Customer Eloquent model. Customer can have multiple WishLists where he / she can add some products. Typical ecommerce functionality.
The point is that Customer can belong to many Users models.
This was easy:
public function users()
{
return $this->belongsToMany(User::class, 'users_sync_customers', 'customer_uuid', 'user_id')
->withTimestamps()
->orderBy('last_name', 'asc');
}
So I can get all Customers assigned for logged in user by
auth()->user()->customers 🎉
As I mentioned, Customer can have multiple Wishlists:
public function wishLists()
{
return $this
->hasMany(WishList::class, 'customer_uuid', 'uuid')
->where('user_id', '=', auth()->user()->id); // <----- this will fail when I log out
}
but WishList is scoped to both Customer UUID and User ID.
Above relationship works but only when I'm logged in obviously.
As soon as I log out the auth()->user()->is is NULL and I get:
ErrorException {#1483 #message: "Trying to get property 'id' of
non-object"
Question: How can I reference in wishLists() the user_id value?
WishList model has this:
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
So can I use something like $this->user->id?
edit:
Nope, this also doesn't work.
you must check that the user is logged in?
Auth::check() ? Auth::user()->id : null

Querying Relationship Existence using multiple MySQL database connections in Laravel 5.2

I am dealing with the following situation: I have two models, an Employee with id and name fields and a Telephone with id, employee_id and flag fields. There is also an one-to-many relationship between these two models, that is an employee may have many telephones and a telephone may belong to a single employee.
class Employee extends Model
{
public function telephones()
{
return $this->hasMany(Telephone::class);
}
}
class Telephone extends Model
{
public function employee()
{
return $this->belongsTo(Employee::class);
}
}
The Employee model references a table employees that exists in database schema named mydb1, while the Telephone model is related to a telephones table that exists in a different database schema named mydb2.
What I want is to fetch only the employees with at least one telephone of a specific flag eager loaded, using Eloquent and (if possible) not the query builder
What I tried so far without success is:
1) use the whereHas method in the Controller
$employees = Employee::whereHas('telephones', function ($query) {
$query->where('flag', 1); //Fetch only the employees with telephones of flag=1
})->with([
'telephones' => function ($query) { //Eager load only the telephones of flag=1
$query->where('flag', 1);
}
])->get();
What I try to do here is first to retrieve only the employees that have telephones with flag=1 and second to eager load only these telephones, but I get the following query exception because of the different db connections used:
Base table or view not found: Table mydb1.telephones doesn't exist (this is true, telephones exists in mydb2)
2) Eager load with constrains in the Controller
$employees = Employee::with([
'telephones' => function ($query) {
$query->where('flag', 1);
},
])->get();
This method eager loads the telephones with flag=1, but it returns all the employee instances, which is not what I really want. I would like to have a collection of only the employee models that have telephones with flag = 1, excluding the models with telephones = []
Taking into account this post, this post and #Giedrius Kiršys answer below, I finally came up with a solution that fits my needs, using the following steps:
create a method that returns a Relation object in the Model
eager load this new relationship in the Controller
filtered out the telephones of flag != 1 using a query scope in the Model
In Employee model
/**
* This is the new relationship
*
*/
public function flaggedTelephones()
{
return $this->telephones()
->where('flag', 1); //this will return a relation object
}
/**
* This is the query scope that filters the flagged telephones
*
* This is the raw query performed:
* select * from mydb1.employees where exists (
* select * from mydb2.telephones
* where telephones.employee_id = employee.id
* and flag = 1);
*
*/
public function scopeHasFlaggedTelephones($query, $id)
{
return $query->whereExists(function ($query) use ($id) {
$query->select(DB::raw('*'))
->from('mydb2.telephones')
->where('telephones.flag', $flag)
->whereRaw('telephones.employee_id = employees.id');
});
}
In the Controller
Now I may use this elegant syntax a’la Eloquent
$employees = Employee::with('flaggedTelephones')->hasFlaggedTelephones()->get();
which reads like "Fetch all the employees with flagged telephones eager loaded, and then take only the employees that have at least one flagged telephone"
EDIT:
After dealing with the Laravel framework for a while (current version used 5.2.39), I figured, that in fact, whereHas() clauses do work in case of the relationship model exists in a different database using the from() method, as it is depicted below:
$employees = Employee::whereHas('telephones', function($query){
$query->from('mydb2.telephones')->where('flag', 1);
})->get();
#Rob Contreras credits for stating the use of the from() method, however it looks like the method requires to take both the database and the table as an argument.
Not sure if this will work but you can use the from method to specify your database connection within the closure:
$employees = Employee::whereHas('telephones', function($query){
$query->from('mydb2')->where('flag', 1);
})->get();
Hope this helps
Dirty solution:
Use whereExists and scope for better readability.
In Your Employee model put:
public function scopeFlags($query, $flag)
{
$query->whereExists(function ($q) use ($flag) {
$q->select(\DB::raw(1))
->from('mydb2.telephones')
->where('telephones.flag', $flag)
->whereRaw('telephones.employee_id = employees.id');
});
}
Then modify your query like so:
$employees = Employee::flags(1)->get();

How add to collection only models with no record joined - Eloquent

In my model Questions I have simple relation to Standpoint
public function standpoints_byrel()
{
// return $this->hasMany('App\Models\Standpoint');
return $this->hasMany('App\Models\Standpoint', 'question_id');
}
Now,
I have yet another model Userattitude (tableuser_attitudes`) which allow users to upvote and downvote Standpoints.
I am able to list Standpoints, which were voted by a given user:
$user_attitudes = Userattitude::join('entitystandpoints', function ($q) use($questionid,$user) {
$q->where('user_attitudes.item_type', '=', 'entitystandpoint');
$q->on('user_attitudes.item_id', '=', 'entitystandpoints.id');
$q->where('entitystandpoints.question_id', '=', $questionid);
$q->where('user_attitudes.creator_id','=', $user);
})
->select('user_attitudes.*')
->get();
TO DO
Now I try to list all standpoints, which were NOT voted by the given user.
I have no idea how to do it using Eloquent.
Any help appreciated.
edit
condition to meet:
if an user votes up or down, a new model Userattitude is created. Therefore Standpoint models not down- or upvoted have nothing to join. still, in the Userattitude there are two fields for upvoting : 'attitude' and 'importance'. often one of them is null
Try with a left join where the left parameter of the join is null.
Something like this (but please check the syntax out, I'm not an Eloquent expert):
$user_attitudes = Userattitude::leftJoin('entitystandpoints', function ($q) use($questionid,$user) {
$q->where('user_attitudes.item_type', '=', 'entitystandpoint');
$q->on('user_attitudes.item_id', '=', 'entitystandpoints.id');
$q->where('entitystandpoints.question_id', '=', $questionid);
$q->where('user_attitudes.creator_id','=', $user);
})
->whereNull('entitystandpoints.id')
->select('user_attitudes.*')
->get();
Let me know.

Eloquent Specify Relation Columns of belongsToMany relationship

I have tried all kinds of methods of limiting the columns which are returned in my many-to-many relationship, and none seem to work.
Background - Not really necessary, but to give the big picture
Essentially, in my app, I want to build a list of contacts for the currently logged in user. Administrator and Billing users should be able to contact everybody including users of group Customer.
Customer should only be able to contact Administrator and Billing.
So my way to tackle this is firstly to determine the groups that the user is in.
$userGroups = Sentry::getUser()->getGroups()->lists('name', 'id');
Then iterate over the groups, to see if the user is in the group Administrator or Billing or Customer and build the contact groups for that user.
foreach($userGroups as $group)
{
if ($group === 'Administrator' || $group === 'Billing')
{
$contactGroups = \Group::with('users')->get(['id', 'name']);
}
else if ($group === 'Customer')
{
$contactGroups = \Group::where('name', 'Administrator')
->orWhere('name', 'Billing')
->with('users')
->get(['id', 'name']);
}
else
{
return Response::json('No Contacts found', 404);
}
}
The problem - It appears that I am unable to select specific columns to select on belongsToMany relations.
I have tried:
$contactGroups = \Group::where('name', 'Administrator')
->orWhere('name', 'Billing')
->with(['users', function($q){
$q->select('id', 'first_name', 'last_name');
}])
->get(['id', 'name']);
I have also tried limiting the select within the Group model
class Group extends Eloquent
{
protected $table = 'groups';
public function users()
{
return $this->belongsToMany('User', 'users_groups')
->select('id', 'first_name', 'last_name', 'email', 'telephone');
}
}
Either way, the query runs, but it returns the entire user object and completely ignores my selects.
As such, when I return a json response, everything that I do not want is included.
So what I have done as a temporary fix is iterate over each of the users in each of the groups, and unset all the attributes which I do not want.
foreach ($contactGroups as $group)
{
foreach($group->users as $user)
{
unset($user->persist_code);
unset($user->created_at);
unset($user->updated_at);
unset($user->deleted_at);
unset($user->last_login);
unset($user->permissions);
unset($user->activated_at);
unset($user->activated);
unset($user->reset_password_code);
unset($user->pivot);
}
}
return Response::json($contactGroups, 200);
This is really clunky, inefficient and seems like a waste of time. Is there a better way of achieving the above?
For some reason selecting specific columns with belongsToMany is not working.
But i have found an alternate solution.
There is a provision in laravel, Converting to Arrays or json that allows you to whitelist/blacklist specific columns when using toArray() or toJson.
To prevent specific fields from appearing in the relation :
class User extends Eloquent{
protected $hidden = array("persist_code","created_at","updated_at","deleted_at","last_login");
}
Instead if you wish to allow specific fields :
protected $visible = array("Visibile fields");
try this
$contactGroups = \Group::where('name', 'Administrator')
->orWhere('name', 'Billing')
->with(['users', function($q){
$q->get(['id', 'first_name', 'last_name']);
}])
->get(['id', 'name']);
or this
$contactGroups = \Group::where('name', 'Administrator')
->orWhere('name', 'Billing')
->with(['users:id,first_name,last_name'])
->get(['id', 'name']);