I have 2 models: User and Transaction with user_id, type, and amount fields. I want to be able to select users with array of transactions grouped by type like so:
{ // User instance
'id': 123,
'username': 'John'
'transactions': [
'type1': '231', // `type`: `sum(amount)`
'type2': '543'
]
}
I will select arrays of users so I need this to load eagerly. How should I write an Eloquent model for that? I've read all docs but still don't know how to approach this.
You Can try Like :
$column = [DB::raw('SUM(order_qty) as volume'),
DB::raw('SUM(order_qty*price) as value')];
$oldValueVolume = OrderProduct::select($column)
->where('order_id', $ordersId)
->first();
User model:
public function transactions()
{
return $this->hasMany(Transaction::class, 'user_id');
}
So then the call:
User::with(
[
'transactions' => function ($q) {
return $q->groupBy(['type', 'user_id'])
->selectRaw('sum(amount) as sum, type, user_id');
}
]
)->get();
Notes
I used 'with' for eager loading.
You mentioned a custom structure (pairs) in a question:
This part
'transactions': [
'type1': '231', // `type`: `sum(amount)`
'type2': '543'
]
That's not what Eloquent does by-default, but you can always remap it.
Related
I have following model:
1- User model
/**
* Define user and functional area relationship
*/
public function functionalAreas()
{
return $this->belongsToMany('App\FunctionalArea', 'user_functional_areas', 'user_id', 'functional_area_id')->withPivot('id', 'is_primary')->withTimestamps();
}
and Business model:
/**
* Define business and user functional area relationship
*/
public function functionalAreas()
{
return $this->belongsToMany('App\FunctionalArea', 'business_functional_areas', 'business_id', 'functional_area_id')->withTimestamps();
}
Now I should take all businesses and users and show them in a single list, for this I'm using from union, following is my query:
public function usersAndOrganizations()
{
$users = $this->users();
$organizations = $this->organizations();
$invitees = $users->union($organizations)->paginate(10);
return response()->json($invitees);
}
private function users()
{
$users = User::byState($approved = true, 'is_approved')
->search()->select([
'id',
DB::raw("CONCAT(first_name, ' ', last_name) AS name"),
'about',
'address',
'slug',
'average_reviews',
DB::raw("'freelancer' AS type")
]);
$users = $users->with([
"functionalAreas" => function ($q) {
$q->select([
'functional_areas.id',
DB::raw("functional_areas.name_en AS name"),
]);
}
]);
return $users;
}
private function organizations()
{
$businesses = Business::where('owner_id', '!=', auth()->user()->id)->verified()
->active()->search()
->select([
'id',
'name',
'about',
'address',
'slug',
'average_reviews',
DB::raw("'business' AS type")
]);
$businesses = $businesses
->with([
"functionalAreas" => function ($q) {
$q->select([
'functional_areas.id',
DB::raw("functional_areas.name_en AS name"),
]);
}
]);
return $businesses;
}
But above query not return the business functional area, its output query use from user relationship instead of business, that with section generate twice the following query:
select
`functional_areas`.`id`,
functional_areas.name_en AS name,
`user_functional_areas`.`user_id` as `pivot_user_id`,
`user_functional_areas`.`functional_area_id` as `pivot_functional_area_id`,
`user_functional_areas`.`id` as `pivot_id`,
`user_functional_areas`.`is_primary` as `pivot_is_primary`,
`user_functional_areas`.`created_at` as `pivot_created_at`,
`user_functional_areas`.`updated_at` as `pivot_updated_at`
from `functional_areas`
inner join `user_functional_areas`
on `functional_areas`.`id` = `user_functional_areas`.`functional_area_id`
where `user_functional_areas`.`user_id` in (2, 6, 7)
But in fact 6, and 7 is business id not user only 2 is user id, one of this queries should use business_functional_areas instead of user_functional_areas.
One more thing found is, all items are inside App\User model in result, its like businesses are also as user object.
The only way for now is to use from map.
public function usersAndOrganizations()
{
$users = $this->users();
$organizations = $this->organizations();
$invitees = $users->union($organizations)->paginate(10);
$invitees = $this->getRelatedData($invitees);
return response()->json($invitees);
}
private function getRelatedData($invitees)
{
$invitees->map(function($object) use($functionalAreaName) {
if($object->type == 'business') {
$relationName = 'businesses';
$relationKey = 'business_id';
$attachableType = Business::MORPHABLE_TYPE;
}
if($object->type == 'freelancer') {
$relationName = 'users';
$relationKey = 'user_id';
$attachableType = User::MORPHABLE_TYPE;
}
$functionalAreas = FunctionalArea::whereHas($relationName, function($q) use ($object, $relationKey){
$q->where($relationKey, $object->id);
})->get([$functionalAreaName.' As name', 'id']);
$object->functional_areas = $functionalAreas->toArray();
});
return $invitees;
}
And remove with from your functions, and call this after you get the paginated result.
In simple words, for now you would not be able to achieve it using Eloquent Eager Loading with Unions. This is not supported yet in Laravel. One of such scenario for which they closed as a Non-Fix issue is Union with Eloquent fail....
Reason: While calling UNION function only the first model(user) is considered main model and model type of result set of other model(Business) passed as argument will be converted to the main one(USER) only and the main model relationship is called on all records(not the desired one).
Due to the above issue only relationship of user model is called on each record of result set. So even for business_id = 1, functional_area of user_id =1 are being fetched.
You can debug more about it from below file & function.
File:
<your_laravel_project>\vendor\laravel\framework\src\Illuminate\Database\Query\Builder.php
Function: get
Alternate Solution
You can fetch the both result set as it is and then merge them after data fetch using php.
public function usersAndOrganizations()
{
$users = $this->users()->get();
$organizations = $this->organizations()->get();
$invitees = $users->toBase()->merge($organizations->toBase())->toArray();
dd($invitees);
}
You can not concat incompatible queries with union.
See Unions.
Your users() method return eloquent builder for User entity.
And organizations() return builder for Business entity.
Thus, it is incorrect to select users and organizations in one query.
The correct query is like that:
SELECT City FROM Customers
UNION
SELECT City FROM Suppliers
ORDER BY City;
I'm trying to retrieve a user's answer. I have this query :
$data = Category::where('id', $id)->with(
[
'questions' => function ($query) {
$query->with(['answers' => function ($query) {
$query->where( 'user_id', auth()->user()->id )->first();
} ])->orderBy('position', 'ASC');
}
]
)->first();
I would like to collect only the answer (a user can only have one answer to a question)
But laravel returns an array "answers"
How to have only the answer, without this array? Thank you !
You can define another relationship called answer and make it a hasOne. This way you only get 1 result, however note that without an order (order by) the result can change from request to request.
I am currently learning Laravel and I am using DB class when joining the three tables. I am able to join the three tables but I need to get the average of a certain column for a teacher (ratings table, rating column), here's what I have and I am stuck here.
Here's my table design
And here's my query
$teachers = DB::table('ratings as r')
->join('users as u','r.teacher_id', '=', 'u.id')
->join('user_infos as ui','r.teacher_id', '=', 'ui.user_id')
->select('r.rating','ui.about','ui.first_name','ui.last_name','ui.avatar','ui.rate','u.*')
->where('u.status', 1)->get();
Also, the results for same user is being repeated. The user has two ratings from the ratings table and it appear two times in my view.
What I want to display here is the list of all teachers and in each card, with their corresponding ratings.. so if I have two teachers in the table, it will display the two teachers and on the right top side of the card is their rating.
Here is a possible solution:
$teachers = DB::table('ratings as r')
->join('users as u','r.teacher_id', '=', 'u.id')
->join('user_infos as ui','r.teacher_id', '=', 'ui.user_id')
->select(DB::raw('AVG(r.rating) as average_rating'),'ui.about','ui.first_name','ui.last_name','ui.avatar','ui.rate','u.*')
->groupBy('r.teacher_id')
->where('u.status', 1)->get();
Ok..since you are using Laravel naming convention/recommendation, I think would be easier/cleaner if you use Eloquent.
I'm not sure if you already created the Eloquent models. And, because of that, I'll put everything here (models, etc).
Ratings model
class Rating extends Model
{
protected $guard = ['id'];
public function teacher()
{
return $this->belongsTo(User::class, 'teacher_id');
}
public function student()
{
return $this->belongsTo(User::class, 'student_id');
}
}
User info model
class UserInfo extends Model
{
protected $guard = ['id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
User model
class User extends Model
{
protected $guard = ['id'];
public function ratings()
{
return $this->hasMany(Rating::class, 'teacher_id');
}
public function infos()
{
return $this->hasMany(UserInfo::class);
}
}
Possible query solution for your problem:
$ratings = Rating::with(['teacher.infos', 'student.infos'])->whereHas('teacher', function($q) {
$q->where('status', true);
})->get();
This will probably give you something like this:
// ratings: [
// "id": 1,
// "teacher_id": 1,
// "student_id": 2,
// ....
// "teacher": {
// "id": 1,
// "name": "...."
// ...
// "infos": [{
// "id": 1,
// "skype": '....'
// ....
// }]
// },
// "student": {
// "id": 2,
// "name": ....,
// "infos": [{
// "id": ...
// }]
// }
// ]
Now you have a collection of Ratings. And, if you need to access the user or the user info, you just need to
// Example:
$firstStudentInfo = $ratings->isEmpty() ? null : $ratings->first()->student->infos;
If you need to calculate something, you can either use an extra query (db) or just a single method on your
collection. I think, in this case, a collection can be faster. You can also create a specific collection
for your "Ratings" model, with specific calculations ("RatingsCollection").
Another example (blade template). Since we already loaded everything "eager load", we don't need to worry
with N+1 query problems here. (https://laravel.com/docs/5.5/eloquent-relationships#eager-loading)
// Ratings grouped by teacher
#foreach ($ratings->groupBy('teacher') as $rating)
Teacher: {{ $rating->avg()...}} or whatever..
#endforeach
If you still want to use DB, #Md Mahfuzur Rahman will do the trick.
:)
I have three tables: users, organizations, organization_user. organization_user is a pivot table for the many-to-many relationship between users and organizations. The many-to-many relationships have been setup correctly in the corresponding models.
I need to obtain all users who are not associated with a given organization. How should this be done using eloquent. Below is what I have tried, but it is returning no results:
public function associate_user($organization_id){
$data = [
'organization' => \App\Organization::find($organization_id),
'users' => \App\User::whereDoesntHave('organizations', function($query) use ($organization_id){
$query->where('organization_id', $organization_id);
})
];
return view('admin.associateUser', $data);
}
You are never actually executing the query.
You need to call get() at the end of the query builder.
public function associate_user($organization_id) {
$data = [
'organization' => \App\Organization::find($organization_id),
'users' => \App\User::whereDoesntHave('organizations', function($query) use ($organization_id){
$query->where('organization_id', $organization_id);
})->get(); // Add the call to get()
];
return view('admin.associateUser', data);
}
Good day!
I have small problem with CakePHP 3. I have association like that:
$this->belongsToMany('AdminUserGroup',[
'classname' => 'AdminUserGroup',
'foreignKey' => 'admin_user_id',
'targetForeignKey' => 'group_id',
'joinTable' => 'AdminUsersGroups'
]);
I am returning records with this code:
public function getAll()
{
$ble = $this->find()
->contain(['AdminUserGroup' ]);
return $ble;
}
Until that it works, but when i want to select specified fields i have problem. When I add select method i don't see columns from contained table:
public function getAll()
{
$ble = $this->find()->select(['id', 'name', 'surname'])
->contain(['AdminUserGroup']);
return $ble;
}
So I added callback query:
public function getAll()
{
$ble = $this->find()->select(['id, name, surname'])
->contain(['AdminUserGroup' => function ($q) { return $q->select(['group_name']);}]);
return $ble;
}
But it still dont work. I can see only fields from main table. Fields with contained table doesn't appear.
{
"id": "8",
"name": "Ola",
"lastname": "BleBle",
"admin_user_group": []
},
Haw can I repair it?
The manual includes the following comments:
When you limit the fields that are fetched from an association, you must ensure that the foreign key columns are selected. Failing to select foreign key fields will cause associated data to not be present in the final result.
and
If you have limited the fields you are loading with select() but also want to load fields off of contained associations, you can pass the association object to select().
and
Alternatively, if you have multiple associations, you can use autoFields().
And there are helpful examples there. So:
$ble = $this->find()
->select(['id', 'name', 'surname'])
->contain(['AdminUserGroup' ])
->autoFields(true);
or if you're using 3.1 or higher:
$ble = $this->find()
->select(['id', 'name', 'surname'])
->select($this->AdminUserGroup);
From the example, it looks like the contain call may not be required in the second version, so I've left it out.
Try something like this.
$ble = $this->find()->select(['Table.id, Table.name, Table.surname'])->contain(['AdminUserGroup']);