Laravel Eloquent Query Select Column From Multiple Tables - php

I have a Laravel backend and a VueJS frontend.
I'm trying to produce a query on a model with a hasOne relationship but only select specific columns from the relationship, do a sum and a group by.
Models
Contract (CONTRACT_TYPE, CONTRACT_PRICE)
AdditionalInfo (START_DATE)
My Eloquent Query
public function contractsByYear() {
return Contract::where('CONTRACT_TYPE','LIKE','SP-%')->with(['AdditionalInfo' => function($query) {
$query->selectRaw('MONTH(START_DATE) as Month')->whereYear('START_DATE','2019');
}])->sum('CONTRACT_PRICE')->groupBy('Month');
}
Expected Results
MONTH | TOTAL
1 | 500
2 | 233
3 | 800
etc
My issue is the table structure is existing and I can't edit. Why the START_DATe is stored in a separate table is beyond me.

Collection methods will probably do what you want
public function contractsByYear()
{
return Contract::select('id', 'CONTRACT_PRICE') /* Only really need the id (or whatever else is the pk) and the price */
->where('CONTRACT_TYPE','LIKE','SP-%')
->with(['AdditionalInfo' => function($query) {
$query->select('id', 'contract_id') /* Only really need the id (or whatever else is the pk), and the Contract fk*/
->selectRaw('MONTH(START_DATE) as Month') /* Aggregate the month */
->whereYear('START_DATE','2019');
}])
->get()
// From this point on, it's a Collection
->groupBy('AdditionalInfo.*.Month') /* It's either this or 'AdditionalInfo.Month' */
->map(function($item, $key) {
return [
'month' => $key,
'total' => $item->sum('CONTRACT_PRICE')
];
});
}

Related

Laravel Eloquent query siblings in same table with eager loading

I have a parent table called patients which has a one-to-many relationship with a child table called notes. (i.e. One patient can have several notes). If given a note, I would like to find other notes for the same patient. Notes are related to patients by a fk called patient_id.
In SQL, I'd do this:
SELECT * FROM notes WHERE patient_id={note.patient_id} AND id <> {note.id}
In Eloquent, I have this:
class Note extends Model
{
public function otherEncounterNotes()
{
return $this->hasMany('App\Note', 'patient_id', 'patient_id')->where('id', '<>',$this->id);
}
...
In my database, the patient with id=1 has two notes with ids 1 and 2, so if I look for the siblings of note id 1, I should get note id 2.
When I use find(), it works as expected, but when I use where(), it returns the original note instead of the sibling. Any ideas?
>>> Note::find(1)->otherEncounterNotes->pluck('id')
=> Illuminate\Support\Collection {#5542
all: [
2,
],
}
>>> Note::where('id',1)->with('otherEncounterNotes')->pluck('id')
=> Illuminate\Support\Collection {#5526
all: [
1,
],
}
Given a Note id, you could obtain the results you want by using the relationship with the Patient model.
$note_id = 1;
// "Pretty" syntax, but it's 3 queries
$sibling_notes = Note::find($note_id) // Query 1
->patient // Query 2
->notes()->where('id', '<>', $note_id)->pluck('id'); // Query 3
Or using a subquery
$note_id = 1;
// A bit messier, 1 query + 1 subquery
$sibling_notes = Note::where('id', '<>', $note_id)
->where('patient_id', function ($subquery) use ($note_id) {
$subquery->select('patient_id')->from('notes')->where('id', $note_id)->limit(1);
})
->pluck('id');
// PHP >= 7.4
Note::where('id', '<>', $note_id)
->where('patient_id', fn($q) => $q->select('patient_id')->from('notes')->where('id', $note_id)->limit(1))
->pluck('id');
The later, you could turn into a query scope
# Note model
public function scopeSiblingsOf($query, $note_id)
{
return $query->where('id', '<>', $note_id)
->where('patient_id', function ($subquery) use ($note_id) {
$subquery->select('patient_id')
->from('notes')
->where('id', $note_id)
->limit(1);
});
}
# Usage
Note::siblingsOf(1)->pluck('id');

Retrieving distinct relationships of relationships using Eloquent in Laravel

Lets assume I have a model for: Race, Participant, Team
And these relations: Race 1-N Participant N-1 Team
See in another way :
races 1 - N participants
teams 1 - N participants
In Laravel terms:
/* Inside Race model */
public function participants()
{
return $this->hasMany(Participant::class);
}
/* Inside Team model */
public function participants()
{
return $this->hasMany(Participant::class);
}
/* Inside Participant model */
public function race()
{
return $this->belongsTo(Race::class);
}
public function team()
{
return $this->belongsTo(Team::class);
}
In the participants table, It looks like this:
id | team_id | race_id | [a lot of other columns...]
------------------------------
1 | 1 | 1 |
2 | 1 | 1 |
3 | 2 | 1 |
4 | 2 | 1 |
In the above example, I know that the race ID 1 has 2 teams.
I can count them by doing this:
$race = Race::find(1);
$number = $race->participants()->distinct()->count('team_id');
The problem
Counting the number of teams is cool, but I want to access to the list of corresponding Team model instances, so that I can use them for further operations (within a foreach loop for example).
I tried a lot of things without success.
Something like this:
$teams = $race->participants()->distinct()->[...] // Don't know what to put here
The working equivalent SQL query is:
SELECT teams.* FROM teams
INNER JOIN participants ON participants.team_id = teams.id
INNER JOIN races ON races.id = participants.race_id
WHERE races.id = 1
GROUP BY teams.id
Resulting in this:
When the participants table contains this:
I want to know if I can do it with Eloquent instead of using Query/Builder DB:: methods directly ?
EDIT 1
My closest result:
$race->participants()->distinct()->get('team_id')
And then using this list of team_id I can access to the teams using Team::find([list of IDs]) but it looks greedy to me.
EDIT 2
I forgot some information:
The participants.team_id column is NULLABLE because:
There are races with teams of participants
There are races with participants (without team)
I don't think you can easily access to it through the $race instance.
But, you can use the whereHas method on the Team model:
$race = Race::find(1);
$teams = Team::whereHas('participants', function ($query) use ($race) {
$query->where('race_id', $race->id);
})->get(); // ->count() also works
See documentation
To get distinct teams from a race model, you could add a many to many relationship between Race and Team with Participant acting as the pivot.
For example,
/* Race */
public function teams()
{
return $this->belongsToMany(Team::class, 'participants', 'race_id', 'team_id');
}
/* Team */
public function races()
{
return $this->belongsToMany(Race::class,'participants', 'team_id', 'race_id');
}
Then, you could just do this
$race = Race::find($id);
$teams = $race->teams()->distinct()->get();
to get a list of distinct teams for the race.
And since we set up the relationship on both models, you can now also get distinct races for each team by doing
$team = Team::find($id);
$teams = $team->races()->distinct()->get();
First, let's improve your eloquent relations.
// Race
public function teams()
{
return $this->hasMany(Team::class);
}
public function withoutTeamParticipants() // change name that suits you
{
return $this->hasMany(Participant::class);
}
// Team
public function participants()
{
return $this->hasMany(Participant::class); // In this case make sure that participant.race_id is null
}
// Participant
public function team()
{
return $this->belongsTo(Team::class); // When no team, then participant.team_id is null
}
public function race()
{
return $this->belongsTo(Race::class);
}
To access list of participants of a given race:
$race = Race::where('id', 1)->with('teams.participants', 'withoutTeamParticipants')->get();
In your blade you can further use this as:
// List of participants without a team
#foreach ($race->withoutTeamParticipants as $participant)
{{ $participant->name }}
#endforeach
// List of participants that are with a team
#foreach ($race->teams as $team)
#foreach ($team->participants as $participant)
{{ $participant->name }}
#endforeach
#endforeach
You can directly use hasManyThrough relationship inside Race model for fetching distinct teams associated with the race.
public function distinctTeams()
{
return $this->hasManyThrough(Team::class, Participant::class, 'race_id', 'id', 'id', 'team_id')->distinct();
}
Now, you can loop over the teams of a race by using $race->distinctTeams
Hopefully this helps.

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

How to filter data from relational table in laravel

I have three table one is orders table and another is order_status table and another is status table. Purpose of order_status table is to keep a track order's events. My table has the following column.
Order table
----------------------------
id | ref_num | name | email |
-----------------------------
Order status table has
---------------------------
order_id | status_id
---------------------
My models are like this
Order model
public function orderStatus(){
return $this->hasMany(OrderStatus::class');
}
Order status model
public function detail(){
return $this->belongsTo(Status::class,'status_id','id');
}
public function order(){
return $this->belongsTo(Order::class);
}
Now i want to get all those order which are still pending.
How can i do so ?
I tried to retrive like this but it failed
$data['orders']= Order::with(['orderStatus' =>function($q){
$q->with('detail')->latest()->where('status_id',2);
}])->latest()->take(10)->get()->toArray();
This return only one after that it does not.
Can anyone please tell me how can i sort this one ??
Thanks
PS:: one order can have multiple status such as unpaid, pending, packageging, in transit and so on but in sequence ofcouse
I added order status table image.. As u can see E7E7FF0EB7 order number has two records 1,and 2 means it was pending and then later stage got delivered.or you can say got processed. where as E02EAEA4BE has only one record of status 1. which means it is still pending.
So i want to get only those which are still pending.Not delivered.
This kinda sound complicated, hope i able to explain properly what i am trying to do.
Your model relations should be changed to a proper many to many. The schemas look correct so I'd make the following changes:
// Order model
public function statuses(){
return $this->belongsToMany(Status::class);
}
// Status model
public function orders(){
return $this->belongsToMany(Order::class);
}
This will pivot correctly on order_status.
To get pending orders the query would be:
Order::whereHas('statuses', function ($query) {
// assuming a 'name' column on statuses table
$query->where('name', 'pending');
// or using dynamic where
// $query->whereName('pending');
})->get();
Alternatively, add a scope to Order model:
public function scopePending($query) {
return $query->with(['statuses' => function ($query) {
$query->where('name', 'pending');
});
});
Usable then as: Order::pending();
Update
Try this to get all those order which are still pending..
$data['orders'] = Order::has('orderStatus', '=', 2)->whereHas('orderStatus', function ($q) {
$q->where('status_id', 2);
})->get()->toArray();
If there is two status records related to one order and one of the status value is 1 then this query will return the order record. You may update it with your exact conditions.(If you are sure that there will be only 2 status related to a order which is still pending then you may remove the second whereHas.
$data['orders'] = Order::has('orderStatus', '=', 2)->get()->toArray();
You may use the many to many relation as #DigitalDrifter suggested. I would also suggest that you should follow many to many relation.
If you are using the many to many relation then you may try the below query..
Order::has('statuses', '=', 2)->WhereHas('statuses', function ($query) {
$query->where('name', 'pending');
})->get();
or
Order::has('statuses', '=', 2)->get();

Laravel base table not found using pivot model

Hi im trying to query my tasks table. A little background information on the tables and how they are related.
Users, create Projects and Tasks, Statuses for projects and tasks can be selected from the status table, users make their own, i plan to have default ones but users may want to create their own.
By default i want to find all the users tasks where the status_name which is held in the statuses table does not equal closed. I decided it would be best to actually create a table called task_status which holds the task_id as well as the status_id. I still want to find the logged in users tasks and then find the status name based on the status_id held in the tasks table, which can be referenced in the statuses table. I then want to only display the any records not equal to closed but the first part which is explained below is trickier than first anticipated.
My table structures can be found below:
Table structure
Users
id | username | email
Tasks
id | user_id | client_id | project_id | status_id | task_name | task_brief
Statuses
id | status_name
Projects
id | user_id | client_id | status_id | type_id | project_name | project_brief
task_status
id | user_id | task_id | status_id
I'm trying to query my db simply first so that I can be sure that the data returned is correct. So I've changed my query to the below:
$user = User::with(array('tasks', 'task.status', 'tasks.taskstatus',
'tasks.clients'))->find(Auth::user()->id);
and I'm trying to return as follows (please bear in mind I also want to query the status table so that I am able to return the name of the status):
#foreach($user->tasks as $task)
{{ $task->task_name }}
#if(!is_null($task->clients))
{{ $task->clients->client_code }}
#endif
#if(!is_null($task->taskstatus))
{{ $task->taskstatus->status_name }}
#endif
#endforeach
My models:
Task.php
public function status(){
return $this->hasOne('Status', 'status_id');
}
public function taskstatus() {
return $this->hasMany('TaskStatus', 'status_id');
}
Status.php
public function tasks()
{
return $this->hasMany('Task');
}
public function taskstatus()
{
return $this->hasMany('TaskStatus', 'status_id');
}
TaskStatus.php
public function tasks()
{
return $this->hasMany('Task', 'task_id');
}
public function status() {
return $this->belongsTo('Status', 'status_id')
}
However using the above returns the following error:
SQLSTATE[42S02]: Base table or view not found: 1146 Table
'imanage.task_statuses' doesn't exist (SQL: select * from `task_statuses`
where `task_statuses`.`status_id` in (?, ?, ?, ?, ?, ?, ?))
(Bindings: array ( 0 => 1, 1 => 2, 2 => 3, 3 => 4, 4 => 5, 5 => 6, 6 => 7, ))
I'm sure that its my relationships that are defined incorrectly but I am not sure how to correct these.Can anyone help?
You can also try this code:
$user = User::whereHas('task.status', function($q)
{
$q->where('status', '!=', 'close');
})
->with('task', 'task.clients')
->where('id', Auth::user()->id)
->first();
Check the eloquent docs on querying relationships.
also remember DO NOT ECHO VIEW, return the view instead.
The error seems related to the fact that Laravel (well, actually Eloquent) is getting the wrong table name: below you have task_statuses instead of task_status. Remember that Eloquent will attempt to pluralize named models to get the table name.
SQLSTATE[42S02]: Base table or view not found: 1146 Table
'imanage.task_statuses' doesn't exist
So either rename your table to match Laravel expectations, or in your model specify a custom table name:
class TaskStatus extends Eloquent {
protected $table = 'task_status';
...
Additionally, this part of your code is unclear to me:
Task.php
public function status(){
return $this->hasOne('Status', 'status_id');
}
public function taskstatus() {
return $this->hasMany('TaskStatus', 'status_id');
}
Does your Task have one status, or does it have many statuses?

Categories