Retrieving records with _____________ relationship? - php

I have 5 tables.
User tabel => This table stores the data of users.
Exam table => This table stores the data of exams.
Feeds table => This table stores the data of feeds.
Exam_User_Pivot table => This table stores the data of users and exams means the user_id and exam_id.
Exam_Feed_Pivot table => This table stores the data of exams and feeds means the exam_id and feed_id.
Now, I want to retrieve the no. of users per each feed ?
I have three models :
1. User Model
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable implements MustVerifyEmail
{
use Notifiable;
protected $primaryKey = 'uid';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password', 'phone',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function exams()
{
return $this->belongsToMany(exam::class, 'exam_user', 'user_id', 'exam_id')->withTimeStamps();
}
}
2. Exam Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class exam extends Model
{
protected $primaryKey = 'eid';
protected $fillable = [
'exam_name',
];
}
3. Feed Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Feed extends Model
{
protected $primaryKey = 'fid';
protected $fillable = [
'feed_type', 'title', 'description', 'feed_media', 'source',
];
public function feedexams()
{
return $this->belongsToMany(exam::class, 'exam_feed', 'feed_id', 'exam_id')->withTimeStamps();
}
}
Users have many-to-many relationship with Exams
Feeds have many-to-many relationship with Exams
I want to retrieve the no. of users per each feed And I don't know which relationship can be use ?

So first add the following two functions to your Exam Model.
Add to Exam model:
public function feeds() {
return $this->belongsToMany('App\Feed')->withTimeStamps(); //
}
public function users() {
return $this->belongsToMany('App\User')->withTimeStamps(); //
}
I want to retrieve the no. of users per each feed .....
The reasoning is based on the following three points:
get the feed,
then get exams of that feed (one feed has many exams)
then get users in each of these exams (here we will use a loop to ensure we count users of each exam from 2 above)
Loop through all feeds as:
foreach(App\Feed::all() as $feed) {
//here you are accessing all related exams using the function feedexams() that you have created in the feeds model
$exams = $feed->feedexams();
//because each exam will have its own users, we need a way to count the users for each exam, and finally ensure we sum or count all of them. we create a counter called $user_per_feed_count to keep or remember the users count as we loop through all exams.
//Finally we will output this counter which will have the total of all users belonging to exams which belong to the feed.
$users_count = 0; //initialize a users counter with zero
foreach($exams as $exam) {
//now we update our counter by adding the number of users for each exam
$users_count += $exam->users()->count();
}
//HERE IS YOUR no. of users per each feed.
echo "Feed " . $feed->name . "has " . $users_count . " users";
}
Expected output will be as follows ...
Feed myfeed has 10 users
Feed otherfeed has 38 users
Now to test it, just go to your routes/web.php and add the following code
Route::get('getusers', function () {
foreach (App\Feed::all() as $feed) {
$exams = $feed->feedexams();
$users_count = 0; //initialize a users counter with zero
foreach ($exams as $exam) {
//$users_count += $exam->users()->count();
$users_count += $exam->has('users') ? $exam->users()->count() : 0;
}
echo "Feed " . $feed->name . "has " . $users_count . " users";
}
});
then open your browser and type this link:
http://YOUR_PROJECT_NAME_HERE/getusers and press enter:
Note: Replace YOUR_PROJECT_NAME_HERE with your actual project name. Make sure you have some exams,users, and feeds already in your database.
MY RESULTS

/**
* Get all of the user for the feed. define in Feed Model
*/
public function feedUsers()
{
return $this->hasManyThrough(App\Models\Exam::class App\Models\User::class);
}
$feeds = App\Feed::all();
foreach($feeds as $feed){
echo $feed->feedUsers->count();
}

Related

Laravel computing user credit balance from transactions table slow [duplicate]

This question already has answers here:
MySQL SUM Query is extremely slow
(3 answers)
Closed last month.
I'm working inside a Laravel 9 project where users can purchase "credits" that are then used each time a user uses the API. However, I need to check the user's remaining quota each time, and right now my query is quite slow.
I have a CreditTransaction model, which stores each transaction in a table with a user_id and a delta column which could be positive or negative depending on whether they purchased credits, or credits were used, then I'm performing a sum of the delta column on my hasMany relationship of credit_transactions, which works, but takes a few seconds to compute when queried.
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable, SoftDeletes;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'role',
'first_name',
'last_name',
'email',
'timezone',
'password',
'origin_source',
'origin_source_other',
'origin_campaign',
'last_login_at',
];
/**
* The attributes that should be hidden for serialization.
*
* #var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
'two_factor_recovery_codes',
'two_factor_secret',
'origin_campaign',
];
/**
* The attributes that should be cast.
*
* #var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'last_login_at' => 'datetime',
];
/**
* Get the credit transactions for the user
*
* #return int
*/
public function getCreditBalanceAttribute()
{
try {
if (!$this->credit_transactions) {
return 0;
}
$balance = $this->credit_transactions->sum('delta');
if ($balance <= 0) {
return 0;
}
return $balance;
} catch (\Exception $err) { }
return 0;
}
/**
* Get the credit transactions for the user
*
* #return array
*/
public function credit_transactions()
{
return $this->hasMany(CreditTransaction::class);
}
}
I can then perform the following to get my current balance.
Auth::user()->credit_balance
I'm not sure how best to proceed with this, as this is based on a table size of 120k rows which is quite small, equally, I need the credit balance to be accurate and fast, so caching it for 15 minutes isn't an option here.
This is my credit_transactions table:
Schema::create('credit_transactions', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->index();
$table->foreignId('credit_type_id')->default(1)->index();
$table->foreignUuid('message_id')->nullable()->index();
$table->integer('delta')->index();
$table->timestamps();
$table->softDeletes();
});
Update your method to get sum of transactions to:
/**
* Get the credit transactions for the user
*
* #return int
*/
public function getCreditBalanceAttribute()
{
try {
// actually this code retrieves all user's transactions from DB
// and then calculate SUM
//if (!$this->credit_transactions) {
// return 0;
//}
//$balance = $this->credit_transactions->sum('delta');
// instead use this:
// Ask database to calculate SUM of delta
$balance = $this->credit_transactions()->sum('delta');
if ($balance <= 0) {
return 0;
}
return $balance;
} catch (\Exception $err) { }
return 0;
}
See more here - Laravel Eloquent Sum of relation's column
To run sum(delta) faster you need to create a multi-column index on the user_id and delta columns.
CREATE INDEX credit_transactions_user_id_delta_idx ON credit_transactions(user_id, delta);
Or just add it to your Laravel migration:
$table->index(['user_id', 'delta']);
dbfiddle example with 20k transactions per user sum took only 7ms.
Is it possible to calculate user balance faster?
Yes, but with different approach, create a column that stores actual user balance. And after that update balance column atomically, e.g.:
begin;
-- add 500 credits to user balance
UPDATE users SET balance = balance + 500;
-- query to log user transactions
-- INSERT INTO credit_transactions (your_column_list) VALUES (your_value_list);
commit;
P.S. See also Is this SQL safe enough to handle user balance transaction?

combine three tables with keyword with in laravel

I want to join three tables. Here is the DB model in server:
lecturers
id - integer (PK)
......
examination
id - integer (PK)
.....
exam_supervision
lecturer_id - integer (FK)
exam_id- integer (FK)
Model relation as I implemented in laravel.
```
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Examsupervision extends Model {
public $timestamps = false;
protected $table = 'exam_supervision';
protected $fillable = ['lecturer_id', 'exam_id'];
}
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Examination extends Model {
public $timestamps = false;
protected $table = 'examination';
protected $fillable = ['name', 'module_id', 'startdate', 'enddate', 'duration', 'type', 'code', 'status'];
}
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Lecturers extends Model {
public $timestamps = false;
protected $table = 'lecturers';
protected $fillable = ['user_id', 'address', 'phone'];
}
```
lecturers, examinations and exam_supervision which is M:M. What I want is find all the examinations of a lecturer.
My code
public function lecturerViewExaminations($id)
{
what code to get all exam for a lecturer..say ID 129
}
Returning me empty set. WHat am I doing wrong? It is easy in SQL syntax but in laravel I am finding it very confusing
What i understand from your question is:
You have one model (Examsupervision) and want the relations examinations and lectures
You can use Eloquent: Relationships
First create a model. In this case your model name would be something like Examsupervision.
In your model(Examsupervision) you can define your relations. In this case examinations and lextures
Create for every relation a new model. So you will have 3 models (Examsupervision, Examinations, Lectures).
Now in your model Examsupervision create a new relation function.
public function lectures()
{
return $this->hasMany('App\Lectures');
}
public function examinations(){
return $this->hasMany('App\Examinations');
}
In your database table "lectures" and "examinations" create a new key with the name examsupervision_id. This key will be the id of the Examsupervision row.
Now in your code when you want the get all the lectures of a given Examsupervision you can do this.
$examsupervision = Examsupervision::find($id); //$id = the Examsupervision you want to retrieve
$lectures = $examsupervision->lectures; //This will return all the lectures connected to examsupervision
And for examinations you can do the same with:
$examsupervision = Examsupervision::find($id); //$id = the Examsupervision you want to retrieve
$examinations = $examsupervision->examinations;
Hope this helps
You are missing ->get() after the last where.
$examinations= Examsupervision::with('lecturers', 'examinations')->where('lecturer_id', 'id')->where('exam_id', 'id')->get();
Model::with() is for relation, Use just ->get()
$examinations=Examsupervision::with('lecturers', 'examinations')->get();
You already have relation for lecturers and examinations.
If not worked, share Model Examsupervision

How to update 'updated_at' of model when updating a pivot table

I'm using laravel and I have a many to many relation between products and orders. There is a pivot table called order-product which has additional information that is updated from time to time. I would like to update the 'updated_at' feild in the order table when the corresponding row in the pivot table is updated or for example if a new product is added.
Is that possible?
The relation between a products and an order is belongsToMany()
Edit: here is some of the code
class Order extends Model
{
protected $table = 'order';
protected $dates = ['updated_at'];
public function products()
{
return $this->belongsToMany(Product::class, 'order_product');
}
}
When I want to remove products its by calling
$order->products()->detach()
So where do I put the $touches array thats mentioned in the laravel docs here
I've tried adding it to the product model but its not working.
You can use the Touching Parent Timestamps, here is an example (from laravel docs).
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* All of the relationships to be touched.
*
* #var array
*/
protected $touches = ['post'];
/**
* Get the post that the comment belongs to.
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}

Laravel Many To Many Relationship getting data

this is my campus model
i have stripped the models for readability
and of course there is join table named as campus_user with id, campus_id, user_id
users can subscribe to campuses
now i want 2 things
1. Get all the users subscribed to a specific campus
2. Check to see if a specific user ( say with id = 1 ) is subscribed to a specific campus ( say with id = 2 )
class Campus extends \Eloquent{
protected $table = "campuses";
public function users(){
return $this->belongsToMany("User");
}
}
// this is my user model
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
protected $table = 'users';
protected $hidden = array('password', 'remember_token');
public function campuses(){
return $this->belongsToMany('\Models\Campus');
}
}
Well the most "Eloquent like" way would be using relationship querying:
http://laravel.com/docs/4.2/eloquent#querying-relations
A simple example would be returning users with any campuses.
// Get users with any campus relationship.
$users = User::has('campuses')->get();
However you need something more powerful.
For 'users' in a specific 'campus'.
// Get 'users' in a 'campus' where the 'name' column equals 'foo'.
$campus = 'foo';
$users = User::whereHas('campuses', function($query) use ($campus) {
$query->where('name', $campus);
})->get();
For a specific 'user' in a specific 'campus' the code would almost be the same.
// Find the 'user' with a primary key of '1' in the 'campus' where
// the 'name' column equals 'foo'.
$primaryKey = 1;
$campus = 'foo';
$users = User::whereHas('campuses', function($query) use ($campus) {
$query->where('name', $campus);
})->find($primaryKey);
As you can see the last example replaced the get() method from the previous example.
You can do the same with the callback in the whereHas() method when you want to query using the primary key. This would result in the following.
...
$query->find($campus);
...
All the methods described above can be found in the Illuminate\Database\Eloquent\Builder class.
I would recommend taking a look at some of the source files to get a better understanding how request are handled.

Laravel save one to many relationship

I have the following relationships set up in Laravel:
OrderStatus Model
- hasMany('Order')
Order Model
- 'belongsTo('OrderStatus');
The database is set up with an orders table and an order_statuses table. The orders table has a field for order_status_id.
When I save an Order, I manually set the order_status_id by fetching the appropriate Order Status model, like this:
$status = OrderStatus::where(['name'=>'sample_status'])->firstOrFail();
$order->order_status_id = $status->id;
$order->save();
I'm wondering if there is a built in function to do this rather than setting the order_status_id manually. I've read about "Attaching a related model", and "Associating Models" in the Laravel docs, but I can't figure out if these fit my use case. I think the issue I'm having is that I'm working directly with the child model (the order), and trying to set it's parent. Is there a function for this?
Sure you can do this:
$status = OrderStatus::where(['name'=>'sample_status'])->firstOrFail();
$order = new Order;
$order->status()->associate($status);
$order->save();
(status() is the belongsTo relation. You might need to adjust that name)
The correct way, to save a relationship for a new related model is as follows:
$status = OrderStatus::where(['name'=>'sample_status'])->firstOrFail();
$order = new Order;
$status->order()->save($order);
Documentation link : http://laravel.com/docs/4.2/eloquent#inserting-related-models
You can go with a custom solution.
I am explaining an example, just coded and very much similar to your question, hope it will help.
I have a Question Model and AnswerOption Model as below.
Question Model
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Question extends Model
{
protected $table = 'questions';
protected $fillable = [
'title',
'created_at',
'updated_at'
];
/**
* Get the answer options for the question.
*/
public function answerOptions()
{
return $this->hasMany('App\Models\AnswerOption');
}
/**
* #param array $answerOptions
*/
public function syncAnswerOptions(array $answerOptions)
{
$children = $this->answerOptions;
$answerOptions = collect($answerOptions);
$deleted_ids = $children->filter(
function ($child) use ($answerOptions) {
return empty(
$answerOptions->where('id', $child->id)->first()
);
}
)->map(function ($child) {
$id = $child->id;
$child->delete();
return $id;
}
);
$attachments = $answerOptions->filter(
function ($answerOption) {
// Old entry (you can add your custom code here)
return empty($answerOption['id']);
}
)->map(function ($answerOption) use ($deleted_ids) {
// New entry (you can add your custom code here)
$answerOption['id'] = $deleted_ids->pop();
return new AnswerOption($answerOption);
});
$this->answerOptions()->saveMany($attachments);
}
}
AnswerOption Model
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AnswerOption extends Model
{
protected $table = 'answer_options';
protected $fillable = [
'question_id',
'title',
'ord',
'created_at',
'updated_at'
];
/**
* Get the question that owns the answer.
*/
public function question()
{
return $this->belongsTo('App\Models\Question');
}
}
Here you can see a single question hasMany answer options, you can see I have used BelongsTo , hasMany relationsip in models.
Now in QuestionController during Question save and update, you can also save the answer options.
For this I have written syncAnswerOptions method in Question Model.
You just need to pass the array of options with id, if id is present already in the database then it will update, if id is blank it will add a new record, If Id was there but not in your new array, then that record will get deleted.
/**
* If you are attaching AnswerOption(s) for the first time, then pass
* in just the array of attributes:
* [
* [
* // answer option attributes...
* ],
* [
* // answer option attributes...
* ],
* ]
*//**
* If you are attaching new AnswerOption(s) along with existing
* options, then you need to pass the `id` attribute as well.
* [
* [
* 'id' => 24
* ],
* [
* // new answer option attributes...
* ],
* ]
*/
In Question controller's store and update method call this method just after question add and update call.
$question->syncAnswerOptions($data['answerOptions']);
$data['answerOptions'] is Array of answer options just like described in the comments.

Categories