Why in pivot table result is different as I expected? - laravel

In laravel 9 app I create many top many relation with table
return new class extends Migration {
public function up()
{
Schema::create('article_vote', function (Blueprint $table) {
$table->id();
$table->foreignId('article_id')->references('id')->on('articles')->onUpdate('RESTRICT')->onDelete('CASCADE');
$table->foreignId('vote_id')->references('id')->on('votes')->onUpdate('RESTRICT')->onDelete('CASCADE');
$table->boolean('active')->default(false);
$table->date('expired_at')->nullable();
$table->integer('supervisor_id')->nullable()->unsigned();
$table->foreign('supervisor_id')->references('id')->on('users')->onDelete('CASCADE');
$table->mediumText('supervisor_notes')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable();
$table->unique(['vote_id', 'article_id'], 'article_vote_vote_id_article_id_index');
$table->index(['vote_id', 'article_id', 'active', 'expired_at'], 'article_vote_vote_id_article_id_active_expired_at_index');
$table->index([ 'expired_at', 'active',], 'article_vote_expired_at_active_index');
$table->index(['created_at'], 'article_vote_created_at_index');
});
Artisan::call('db:seed', array('--class' => 'articleVotesWithInitData'));
}
In app/Models/Vote.php :
public function articles(): BelongsToMany
{
return $this->belongsToMany(Article::class, 'article_vote', 'vote_id')
->withTimestamps()
->withPivot(['active', 'expired_at', 'supervisor_id', 'supervisor_notes']);
}
and in app/Models/Article.php :
public function votes(): BelongsToMany
{
return $this->belongsToMany(Vote::class, 'article_vote', 'article_id')
->withTimestamps()
->withPivot(['active', 'expired_at', 'supervisor_id', 'supervisor_notes']);
}
Running requests:
$article = Article::getById(2)
->firstOrFail();
$articleVotes = $article->votes;
I got sql :
SELECT `votes`.*, `article_vote`.`article_id` AS `pivot_article_id`, `article_vote`.`vote_id` AS `pivot_vote_id`, `article_vote`.`created_at` AS `pivot_created_at`, `article_vote`.`updated_at` AS `pivot_updated_at`, `article_vote`.`active` AS `pivot_active`, `article_vote`.`expired_at` AS `pivot_expired_at`, `article_vote`.`supervisor_id` AS `pivot_supervisor_id`, `article_vote`.`supervisor_notes` AS `pivot_supervisor_notes`
FROM `votes`
INNER JOIN `article_vote` on `votes`.`id` = `article_vote`.`vote_id`
WHERE `article_vote`.`article_id` = 2
But result is different as I expected, as in article_vote table I have rows : https://prnt.sc/wTE5uaPrQu9v
But I see different with the sql : https://prnt.sc/Os14x5K6unyu
Why 4 rows with different vote id ?
Thanks!

Comparing your two screenshots, it seems like different databases, look at the created_at and updated_at values of the pivot table, they are totally different for all the rows. Could that be a mistake that you're querying, for example, local vs live DBs?

Related

How with pivot relation set additive filter?

In laravel 9 app I use many to many relation with table article_vote joining 2 tables :
return new class extends Migration {
public function up()
{
Schema::create('article_vote', function (Blueprint $table) {
$table->id();
$table->foreignId('article_id')->references('id')->on('articles')->onUpdate('RESTRICT')->onDelete('CASCADE');
$table->foreignId('vote_id')->references('id')->on('votes')->onUpdate('RESTRICT')->onDelete('CASCADE');
...
});
}
In app/Models/Article.php model I have relation :
public function onlyActiveVotes(): BelongsToMany
{
return $this->belongsToMany(Vote::class, 'article_vote', 'article_id')
->withTimestamps()
->wherePivot('active', true)
->withPivot(['active', 'expired_at', 'supervisor_id', 'supervisor_notes']);
}
and I want having Article by Id($id)
$article = Article::getById($id)
->firstOrFail();
Using onlyActiveVotes relation to get filtered data from vote :
$voteTableName = ((new Vote)->getTable());
$articleVotes = $article->onlyActiveVotes()::whereHas('votes', function ($query) use($request, $voteTableName) {
$query->where($voteTableName . '.vote_category_id', $request->filter_vote_vote_category_id);
})->get();
It does not work, as I got error :
Method Illuminate\Database\Eloquent\Relations\BelongsToMany::whereHas does not exist. i
Line below returns collection
dd($article->onlyActiveVotes );
Line below returns BelongsToMany:
dd($article->onlyActiveVotes() );
Illuminate\Database\Eloquent\Relations\BelongsToMany {#2283 // app/Repositories/ArticleToManyVotesRepository.php:74
#query: Illuminate\Database\Eloquent\Builder {#2218
#query: Illuminate\Database\Query\Builder {#2268
On
dd($article->onlyActiveVotes()->query );
I got error:
Cannot access protected property Illuminate\Database\Eloquent\Relations\BelongsToMany::$query
If there is a way to use whereHas with onlyActiveVotes relation ?
Updated BLOCK :
I hope I clearly exoplaned what I want : want to get only filtered votes which arerelated with pivot through $article model
Aftet I fixed :
$filteredArticleVotes = $article->onlyActiveVotes()->whereHas
I got other error :
Call to undefined method App\Models\Vote::onlyActiveVotes()
pointing at line
$filteredArticleVotes = $article->onlyActiveVotes()->whereHas('onlyActiveVotes', function ($query) use($request, $voteTableName) {
$query->where($voteTableName . '.vote_category_id', $request->filter_vote_vote_category_id);
})->get();
As I wrote in my post Article model has onlyActiveVotes method and I expected the code above have to work, but it did not...
Thanks!
Since onlyActiveVotes() already returns a query builder for the votes table, you can directly chain the where() method to filter the results by the vote_category_id column. The whereHas() method is not necessary in this case.
$articleVotes = $article->onlyActiveVotes()
->where('vote_category_id', $request->filter_vote_vote_category_id)
->get();

how to retrieve limited number of related model and sort collection by related model in laravel?

I have 3 model
Shop model, Product model, Picture model
I want to retrieve a collection of shops with last 3 Product model with their pictures and sort my collection based on newest product.
I tried leftjoint and joint in laravel 6 to be able to sort the result but i get all shops`product (i only need last 3 product for each shop),
when I use joint I cant retrieve product pictures
I also have tried “with” method in laravel , I couldnt sort the result based on product.creatred_at and also i get all related product in this method too.(as i mentioned i need the last 3 product)
class Shop extends Model
{
public function products()
{
return $this->hasMany('App\Product');
}
}
class Product extends Model
{
public function shop()
{
return $this->belongsTo('App\Shop');
}
public function pictures()
{
return $this->morphMany('App\hPicture', 'pictureable');
}
}
Shop::select('shops.*', 'products.id', 'products.shop_id', 'products.name as pname', 'products.user_id', 'products.code', 'products.price')
->with(['pictures', 'products.pictures'])
->leftjoin('products', function ($leftJoin) {
$leftJoin->on('shops.id', '=', 'products.shop_id');
});
$dataList = $dataList->orderBy($field, $order);
$dataList = $dataList->paginate(5)->appends(['sortField' => $field, 'sortOrder' => $order]);
the table layout for product and shop model is:
Schema::create('shops', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('slug');
$table->string('phone')->nullable();
$table->string('address')->nullable();
$table->timestamps();
$table->string('description')->nullable();
$table->uuid('uuid');
});
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->integer('shop_id')->unsigned();
$table->foreign('shop_id')->references('id')->on('shops');
$table->string('name');
$table->string('code');
$table->string('slug');
$table->integer('price');
$table->uuid('uuid');
$table->timestamps();
});
There are only 2 ways of resolving this:
Either you pull in all products, and trim them in the end(advisable only if not too many products per shop):
$shops = Shop::with(['products' => function($subQuery) {
$subQuery
->with('pictures') //Running in scope of product, also load each product's pictures
->orderBy('created_at', 'desc');
}])
->get();
foreach ($shops as $shop) {
$shop->setRelation('products', $shop->products->take(3));
}
NOTE:
You will load every single product that is linked to the shops you load. You could get memory issues with this.
Take only what you need, but introduce a n+1 query issue(advisable only with small quantities of $shops:
$shops = Shop::get();
foreach ($shops as $shop) {
$shop->load([
'products' => function($query) {
$query
->orderBy('created_at', 'desc')
->limit(3)
->get();
}]);
}
NOTE:
N+1 query problem: You are performing a new query for each shop, so if you have a million shops, it will be a million extra queries.
EDIT: (answering comment question)
Q: How can i sort $shops based on their latest product created_at field?
$sortedShops = $shops->sortBy(function ($shop, $key) {
return $shop->products->first()->created_at;
})->values()->all();
sortBy is called on the collection(not uery). It allows you to go over each element(in this case shops) and use each object. Please do note that this function will fail if you have no products linked to the shop.
The ->values()->all() at the end makes sure that when you convert your shops to json, you will create an array, and not an object in js.
Source:
https://laravel.com/docs/7.x/collections#method-sortby
EDIT: (deleted original answer as it did not work)
Previous answer does not work, because limit(3) will limit the total amound of products loaded, in stead of 3 products per shop(my bad).

Laravel: How to get data from 3 tables with relationship

I have 3 Tables:
Customers
id
name
Sales
customer_id
sale_date
Contacts
customer_id
contact_date
There aren't any update operations in the contacts table. Each process opens a new record in the contacts table. So, a user can have more than one records in the contacts table.
Here are my relations in models:
Customer
public function contacts()
{
return $this->hasMany(Contact::class);
}
public function sales()
{
return $this->hasMany(Sale::class);
}
Contact
public function customer()
{
return $this->belongsTo('App\Customer', 'customer_id');
}
Sale
public function customer()
{
return $this->belongsTo('App\Customer');
}
I would like to have the latest record of the contacts table and make it join with the other related tables.
Here is the query which I have tried:
$record = Contact::groupBy('customer_id')
->select(DB::raw('max(id)'));
$result = Customer::query();
$result->where('is_active', 'YES');
$result->with('sales');
$result->whereHas('contacts', function ($q) use($record){
return $q->whereIn('id', $record)->where('result', 'UNCALLED');
});
return $result->get();
In the blade file, I get some result in foreach loops. However, I am unable to get the related data from the sales and contacts table.
#foreach($result as $item)
#foreach($item->sales as $sale) // Has no output and gives error: Invalid argument supplied for foreach()
#foreach($item->contacts as $contact) // Has no output and gives error: Invalid argument supplied for foreach()
Can anyone help me how to display the sale and contact date? Or any idea for how to improve this code quality?
If you want the latest record of the contacts you can declare another relationship on the Customer model, e.g.:
public function latest_contact()
{
return $this->hasOne(Contact::class)->latest('contact_date');
}
BTW you can always declare one or more hasOne additional relationship if you have a hasMany in place the foreign key used is the same.
In this way you can retrieve latest_contact eager loaded with your Customer model:
$customer = Customer::with('latest_contact')->find($id);
Or use this relationship in your queries, something like that:
$customers = Customer::where('is_active', 'YES')
->with('sales')
->with('contacts')
->whereHas('last_contact', function ($q){
return $q->where('result', 'UNCALLED');
})->get();
Or that:
$customers = Customer::where('is_active', 'YES')
->with('sales')
->with('contacts')
->with('last_contact', function ($q){
return $q->where('result', 'UNCALLED');
})->get();
If you want you can declare last_contact with the additional where:
public function latest_contact()
{
return $this->hasOne(Contact::class)
->where('result', 'UNCALLED')
->latest('contact_date');
}
This way all other queries should be easier.
I hope this can help you.
I'm not sure, but can you try to do the following:
return Customer::where('is_active', 'YES')
->with([
'sale',
'contact' => function ($query) use($record) {
return $query->whereIn('id', $record)->where('result', 'UNCALLED');
}
])->get();

Laravel 4 Eager Load Constraints with BelongsTo

I have two Models... Aircraft and Movement:
In Aircraft:
public function movements() {
return $this->hasMany('Movement');
}
In Movement:
public function aircraft() {
return $this->belongsTo('Aircraft');
}
Table Movement:
Schema::create('movements', function($table)
{
$table->increments('id');
$table->boolean('flag_visible')->default(1);
$table->integer('aircraft_id')->nullable()->default(NULL);
$table->timestamps();
});
Table Aircraft:
Schema::create('aircrafts', function($table)
{
$table->increments('id');
$table->string('identifier');
$table->timestamps();
});
Now I want select all movements with Aircrafts with identifiers = 10:
$movements = Movement::with(array(
'aircraft' => function($query) {
$query->where('identifier', 'like', '%10%');
}
));
I Have only ONE movement record with an aircraft_id where the Identifier is %10%.
But I get ALL movements records, only the one with the right identifier has the relationship "aircraft". But I want an array only with ONE record, only the one with the right identifier.. what is wrong here?
with() creates a separate query to retrieve all the related values at once (eager loading) instead of as-needed (lazy loading).
You're looking to add a constraint on the movement query, so you should use whereHas(), something like this:
$movements = Movement::whereHas('aircraft', function($q)
{
$q->where('identifier', 'like', '%10%');
})->get();

Having problems making a query in one to many Relationship in Laravel 4

This is pretty weird and I have no idea what i'm doing wrong.
I have 2 models:
class Project extends Eloquent {
public function status()
{
return $this->belongsTo('ProjectStatus','status_id');
}
}
and
class ProjectStatus extends Eloquent {
protected $table = 'PStatus';
public function projects()
{
return $this->hasMany('Project');
}
}
The table "projects" has the proper foreign keys:
Schema::create('PStatus', function($table) {
$table->increments('id');
$table->string('name', 64);
$table->unique('name');
});
Schema::create('Projects', function($table) {
$table->increments('id');
$table->string('name', 100);
$table->integer('status_id')->unsigned();
$table->foreign('status_id')->references('id')
->on('PStatus')
->onDelete('cascade');
});
In the database (for example) I have only 1 project: "Project_1" (id = 1) with status_id = 1 (Lets say status name = "Open"). If I execute the following query:
$projects = Project::with(array('status' => function($query){
$query->where('name', '<>', 'Open');
}))->get();
I'm still getting the project in the results!!. This is the sql log:
array (size=3)
'query' => string 'select * from `PStatus` where `PStatus`.`id` in (?) and `name` <> ?' (length=67)
'bindings' =>
array (size=2)
0 => int 1
1 => string 'Open' (length=4)
'time' => float 0.36
if I print:
var_dump($projects->count());
I still get 1 project! How come?
I can easily solve my problem by changing the query to something like:
$projects = Project::where('status_id', '<>', 1)->get(); //assuming that status_id=1 => "Open"
but i prefer not to use ids directly when I guess the method with should work. What am I doing wrong here???
Actually this is the answer:
Eloquent Nested Relation with Some Constraint
A huge confusion with the with method. I should have used a join.

Resources