Query an object via multiple pivot tables with Laravel's ORM Eloquent - php

What I want to do, is to retrieve data through multiple relationships (pivot tables).
I have three database tables
users, cities, and jobs.
They are built like this (They aren't, just to give you a glimpse)
Users table
id int(11) PK, Autoincrement,
name varchar(255)
Cities table
id int(11) PK, Autoincrement,
name varchar(255)
Jobs table
id int(11) PK, Autoincrement,
name varchar(255)
Now I have pivot tables, because these are many to many relationships. I got the tables city_user and job_user.
CityUser table
city_id int(11),
user_id int(11)
JobUser table
job_id int(11),
user_id int(11)
In every Class (User, City, Job), I got the relationships defined with a belongsToMany method.
City class
/**
* Defines the relation between the city and its users
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function users()
{
return $this->belongsToMany(User::class, 'city_user');
}
User class
/**
* Defines the relation between the user and its cities
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function cities()
{
return $this->belongsToMany(City::class, 'city_user');
}
The same goes for the jobs table. What I now want to do is to get all users that are in a specific city and have a specific job.
Let's imagine I want to query all users who live in City with the ID 5, and have the job with the ID 10.
Going for one relationshiop is fairly easy
$users = City::find(5)->users;
or
$users = Job::find(10)->users;.
But, how do I achieve that for multiple relationships in this case? I tried to do it like this
$users = City::find(10)->with(['users' => function($query) use ($jobId) {
// Here I'm stuck. I wouldn't know how to query further? Maybe like this
return $query->with('jobs')->whereJobId($jobId);
}]);
But, when I do it like this, I'm getting 0 results, so there must be something wrong. Any ideas?
I'm getting an error though
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42S22]: Column not found: 1054 Unknown column 'job_id' in 'where clause'' in
I also tried it like this (inside the with(['consultants']))
$query->with(['jobs' => function ($query) use ($jobId) {
$query->whereJobId($jobId);
}]);
but that doesn't look right. Also I'm getting a Collection of Cities, but I need one of Users.
Then I tired it (temporary) like this
$users = User::with(['cities' => function ($query) use($cityId) {
/** #var \Illuminate\Database\Eloquent\Relations\BelongsToMany $query */
$query->wherePivot('city_id', $cityId);
}])->get();
But, then I'm getting ALL users, instead of just the ones, which is even more confusing, because the documentation says
Sometimes you may wish to eager load a relationship, but also specify additional query constraints for the eager loading query. Here's an example:
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%');
}])->get();
In this example, Eloquent will only eager load posts that if the post's title column contains the word first. Of course, you may call other query builder to further customize the eager loading operation:
But why doesn't it work then?

You can solve your fatal error this way:
$users = City::find(10)->with(['users.jobs', function($query) use ($jobId) {
$query->whereJobId($jobId);
});
}]);
But this will not filter the users for a specific job ID. For example we have two users with IDs 1 and #2. User 1 is related to job 10 while user 2 is not. The code above will give you both users for $jobId = 10, because the users query will be executed first without any filters for jobs and the jobs query will be executed after the users query.
You have to use the join() method of the query builder:
$users = User::join('job_user', 'users.id', '=', 'job_user.user_id')
->join('city_user', 'users.id', '=', 'city_user.user_id')
->where('job_user.job_id', $jobId)
->where('city_user.city_id', $cityId)
->get();

Related

Why is the request not working correctly when using BelongsToMany Laravel?

I have 2 models in my app, 'Subject' & 'Professor' (each Subject belongs to many Professors).
I made the many-to-many relation between two model using belongsToMany(). belongsToMany() doesn't work.
I'm trying to get data like this:
$subjects = Subject::with(["professors"])->whereHas("professors", function ($q){ $q->where("id", \request("professor_id")); })->get();
Error:
"SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'id' in where clause is ambiguous (SQL: select * from `subjects` where exists (select * from `professors` inner join `subjects_of_professor` on `professors`.`id` = `subjects_of_professor`.`professor_id` where `subjects`.`id` = `subjects_of_professor`.`subject_id` and `id` = 39))",
Does anyone know where did I make a mistake?
Here's the code to Models:
class Subject extends Model
{
public function professors(): BelongsToMany
{
return $this->belongsToMany(Professor::class, "subjects_of_professor");
}
}
class Professor extends Model
{
public function subjects(): BelongsToMany
{
return $this->belongsToMany(Subject::class, "subjects_of_professor");
}
}
And here is my database structure:
subjects:
id
title
subjects_of_professor:
id
subject_id
professor_id
professors:
id
name
description
I'm found mistake, i was needed to add table in my code, when i trying to get data:
$subjects = Subject::whereHas("professors", function ($q){ $q->where("professors.id", \request("professor_id")); })->get();

Laravel Eloquent finding models with more than one relation to another model on another table [duplicate]

This question already has answers here:
laravel BelongsTo relationship with different databases not working
(13 answers)
Closed 2 years ago.
I have two models using different tables on two different connections, User and UserInfo.
User has a UserInfo hasMany relation:
public function userInfo()
{
return $this->hasMany('path\to\UserInfo','User_ID');
}
and UserInfo has a User belongsTo relation:
public function user()
{
return $this->belongsTo('anotherpath\to\User', 'User_ID', 'User_ID');
}
I would like to find the first user with more than one UserInfo, however I believe that because they are on different tables on the database I am having issues.
This call
$patient = Patient::with('UserInfo')
->withCount('UserInfo')
->having('UserInfo_count', '>', 1)
->first();
Works as expected for other relations and is what I am trying to achieve, but not with this one. The difference being that the two models are on different tables. When I call this in Tinker, I get the error:
Illuminate/Database/QueryException with message 'SQLSTATE[42S02]:
Base table or view not found: 1146 Table '(TableName).UserInfo'
doesn't exist (SQL: select `User`.*, (select count(*) from `UserInfo`
where `User`.`User_ID` = `eob`.`User_ID`) as `UserInfo_count `User`
having `UserInfo_count` > 1 limit 1)'
Any ideas? I'm very new to eloquent and Laravel in general, sorry if I've gotten any terminology wrong or am missing something simple. Thanks!
maybe your table names are not defined properly as standard. so you can use table property to bind table name in model.
what is the standard to define table name.
Illuminate/Database/Eloquent/Model.php
/**
* Get the table associated with the model.
*
* #return string
*/
public function getTable()
{
if (isset($this->table)) {
return $this->table;
}
return str_replace('\\', '', Str::snake(Str::plural(class_basename($this))));
}
Example
Table Model
users User
user_profiles UserProfile
Alternative
in your UserInfo model
protected $table = 'your table name';
In more thing you don't need to add with() method and with withCount() method.
$patient = Patient::withCount('UserInfo')->having('UserInfo_count', '>', 1)->first();

Laravel - Displaying fields with many to many relationship according field in pivot table

I have this database structure
table users table office_user table offices
----------- ----------------- -------------
id * id * id *
full_name user_id name
office_id
joined_at
So in my project every office has many users and user can be joined to many offices in date (joined_at)
User.php model
public function offices()
{
return $this->belongsToMany('App\Office)->withPivot('joined_at');
}
Office.php model
public function users()
{
return $this->belongsToMany('App\User)->withPivot('joined_at');
}
OfficeController.php
public function show(Office $office)
{
$users = User::with(array('phones', 'offices' , function($query)
{
$query->orderBy('joined_at', 'desc');
}))->get();
return view('dashboard.offices.show', compact(['office', 'users']));
}
I need two things :
1- Get current users list for every office
2- Count of current users in every office
I already achieve this:
<h3>{{ $office->name }}</h3><span>{{ $office->users->count() }}</span>
#foreach ($office->users as $user)
<li>{{ $user->full_name }}</li>
#endforeach
But the result is not as expected it gives me all users in certain office and count of them regardless there joined date
I want the list of last joined users to this office and count of them according joined_at field in pivot table
Thank you and Sorry for my english
But the result is not as expected it gives me all users in certain office and count of them regardless there joined date
When you do $office->users->count() that is the expected behavior because you are retrieve all the associated users of every office at any time, so given that you returned all this users, the count() executed in the collection will count all of them.
Your pivot attribute is just a timestamp, so how would you reduce the number of users returned? users that joined the office today/in the last hour/in the last 15 min maybe?
If so, you can add constrains to your count() method to get the results you want.
As an example, in the following lines we are gonna constraint the associated offices that has a joined_at that belongs to today:
public function show(Office $office)
{
$users = User::with([
'phones',
'offices' => function ($offices) {
$offices->whereDate('joined_at', '>', now()->startOfDay());
},
])->get();
return view('dashboard.offices.show', compact([office, 'users']));
}
Check this section of the documentation:
Constraining Eager Loads
Sometimes you may wish to eager load a relationship, but also specify
additional query conditions for the eager loading query. Here's an
example:
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%');
}])->get();
In this example, Eloquent will only eager load posts where the post's
title column contains the word first. You may call other query
builder methods to further customize the eager loading operation:
$users = App\User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->get();

Laravel 5 - Find Fields on "MorphOne" Relationship

I'm quite new to Laravel and I'm now facing this issue while trying to create a query:
I have the following Morphable classes:
\App\User
class User {
public function userable()
{
return $this->morphTo();
}
}
\App\Distributor
class Distributor {
public function user()
{
return $this->morphOne('App\User', 'userable');
}
}
user table has the fields: name, email, status, userable_type and userable_id.
distributor table has the fields: store_code and location_id.
By using Eloquent, i need to start the query from Distributor model and select only the following fields: 'name, email, store_code'.
I'm trying the following, but laravel says user.name doesn't exists :(
$queryBuilder = \App\Distributor::has('user');
$queryBuilder->select(['user.name']);
$queryBuilder->get();
QueryException in Connection.php line 651:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'user.name' in 'field list' (SQL: select user.name from distributor where (select count(*) from user where user.userable_id = distributor.id and user.userable_type = App\Distributor and user.deleted_at is null) >= 1)
I was able to achieve my goal forcing the join relationship, but this seems wrong, I think Eloquent is able to find the relation by itself as the Morph relationship is specified in the Model.
Just for record, this works good:
$queryBuilder = \App\Distributor::has('user');
$queryBuilder->join('user', function($join) {
$join->on('userable_id', '=', 'distributor.id')
->where('userable_type', '=', \App\Distributor::class);
});
$queryBuilder->select(['user.name']);
$queryBuilder->get();
Also, since its a one-to-one like relationship, sometimes I'll need to order the results using one of the users columns
But I need another way to do it without forcing the join, something clean as the first example.
read about with() function in the docs
$queryBuilder->select('id','store_code');
$queryBuilder = \App\Distributor::with(['user'=>function($query){
$query->select('id','name','email')
}]);
$queryBuilder->has('user');
$queryBuilder->get();

Laravel eager loading with limit

I have two tables, say "users" and "users_actions", where "users_actions" has an hasMany relation with users:
users
id | name | surname | email...
actions
id | id_action | id_user | log | created_at
Model Users.php
class Users {
public function action()
{
return $this->hasMany('Action', 'user_id')->orderBy('created_at', 'desc');
}
}
Now, I want to retrieve a list of all users with their LAST action.
I saw that doing Users::with('action')->get();
can easily give me the last action by simply fetching only the first result of the relation:
foreach ($users as $user) {
echo $user->action[0]->description;
}
but I wanted to avoid this of course, and just pick ONLY THE LAST action for EACH user.
I tried using a constraint, like
Users::with(['action' => function ($query) {
$query->orderBy('created_at', 'desc')
->limit(1);
}])
->get();
but that gives me an incorrect result since Laravel executes this query:
SELECT * FROM users_actions WHERE user_id IN (1,2,3,4,5)
ORDER BY created_at
LIMIT 1
which is of course wrong. Is there any possibility to get this without executing a query for each record using Eloquent?
Am I making some obvious mistake I'm not seeing? I'm quite new to using Eloquent and sometimes relationship troubles me.
Edit:
A part from the representational purpose, I also need this feature for searching inside a relation, say for example I want to search users where LAST ACTION = 'something'
I tried using
$actions->whereHas('action', function($query) {
$query->where('id_action', 1);
});
but this gives me ALL the users which had had an action = 1, and since it's a log everyone passed that step.
Edit 2:
Thanks to #berkayk looks like I solved the first part of my problem, but still I can't search within the relation.
Actions::whereHas('latestAction', function($query) {
$query->where('id_action', 1);
});
still doesn't perform the right query, it generates something like:
select * from `users` where
(select count(*)
from `users_action`
where `users_action`.`user_id` = `users`.`id`
and `id_action` in ('1')
) >= 1
order by `created_at` desc
I need to get the record where the latest action is 1
I think the solution you are asking for is explained here http://softonsofa.com/tweaking-eloquent-relations-how-to-get-latest-related-model/
Define this relation in User model,
public function latestAction()
{
return $this->hasOne('Action')->latest();
}
And get the results with
User::with('latestAction')->get();
I created a package for this: https://github.com/staudenmeir/eloquent-eager-limit
Use the HasEagerLimit trait in both the parent and the related model.
class User extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}
class Action extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}
Then simply chain ->limit(1) call in your eager-load query (which seems you already do), and you will get the latest action per user.
My solution linked by #berbayk is cool if you want to easily get latest hasMany related model.
However, it couldn't solve the other part of what you're asking for, since querying this relation with where clause would result in pretty much the same what you already experienced - all rows would be returned, only latest wouldn't be latest in fact (but latest matching the where constraint).
So here you go:
the easy way - get all and filter collection:
User::has('actions')->with('latestAction')->get()->filter(function ($user) {
return $user->latestAction->id_action == 1;
});
or the hard way - do it in sql (assuming MySQL):
User::whereHas('actions', function ($q) {
// where id = (..subquery..)
$q->where('id', function ($q) {
$q->from('actions as sub')
->selectRaw('max(id)')
->whereRaw('actions.user_id = sub.user_id');
})->where('id_action', 1);
})->with('latestAction')->get();
Choose one of these solutions by comparing performance - the first will return all rows and filter possibly big collection.
The latter will run subquery (whereHas) with nested subquery (where('id', function () {..}), so both ways might be potentially slow on big table.
Let change a bit the #berkayk's code.
Define this relation in Users model,
public function latestAction()
{
return $this->hasOne('Action')->latest();
}
And
Users::with(['latestAction' => function ($query) {
$query->where('id_action', 1);
}])->get();
To load latest related data for each user you could get it using self join approach on actions table something like
select u.*, a.*
from users u
join actions a on u.id = a.user_id
left join actions a1 on a.user_id = a1.user_id
and a.created_at < a1.created_at
where a1.user_id is null
a.id_action = 1 // id_action filter on related latest record
To do it via query builder way you can write it as
DB::table('users as u')
->select('u.*', 'a.*')
->join('actions as a', 'u.id', '=', 'a.user_id')
->leftJoin('actions as a1', function ($join) {
$join->on('a.user_id', '=', 'a1.user_id')
->whereRaw(DB::raw('a.created_at < a1.created_at'));
})
->whereNull('a1.user_id')
->where('aid_action', 1) // id_action filter on related latest record
->get();
To eager to the latest relation for a user you can define it as a hasOne relation on your model like
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class User extends Model
{
public function latest_action()
{
return $this->hasOne(\App\Models\Action::class, 'user_id')
->leftJoin('actions as a1', function ($join) {
$join->on('actions.user_id', '=', 'a1.user_id')
->whereRaw(DB::raw('actions.created_at < a1.created_at'));
})->whereNull('a1.user_id')
->select('actions.*');
}
}
There is no need for dependent sub query just apply regular filter inside whereHas
User::with('latest_action')
->whereHas('latest_action', function ($query) {
$query->where('id_action', 1);
})
->get();
Migrating Raw SQL to Eloquent
Laravel Eloquent select all rows with max created_at
Laravel - Get the last entry of each UID type
Laravel Eloquent group by most recent record
Laravel Uses take() function not Limit
Try the below Code i hope it's working fine for u
Users::with(['action' => function ($query) {
$query->orderBy('created_at', 'desc')->take(1);
}])->get();
or simply add a take method to your relationship like below
return $this->hasMany('Action', 'user_id')->orderBy('created_at', 'desc')->take(1);

Categories