Laravel 4.2 n+1 without eager loading - php

I have a table with products
products
------------------------------------------------
id product_code purchase_type quantity
1 106 new 1
2 107 renew 3
and a pricelist for products
price_list
----------------------------------------------------------------------
id product_code purchase_type minimum maximum unit_price
1 106 new 1 25 20
2 106 new 26 50 16
3 106 new 51 100 14
When I display the products on a page, I need to display the price with the formula
getPrice(product_code, purchase_type, quantity) for every product
Using
foreach(Product::all() as $product){
{{ $product->product_code }}
{{ $product->purchase_type }}
{{ $product->quantity }}
{{ PriceList::getPrice($product->product_code, $product->purchase_type, $product->quantity) }} - this one is the n+1 problem
}
PriceList::getPrice() is something like:
public static function getPrice($productCode, $purchaseType, $quantity){
return PriceList::where('product_code', $productCode)
->where('purchase_type', $purchaseType)
->where('minimum', '>=', $quantity)
->where('maximum', '<=', $quantity)
->get()->first()->unit_price;
}
I can't seem to find a more efficient way.
And when I display over 1000 products, then it gets really slow.
I can't seem to find a way to use eager loading.

It is not a good idea to call model from inside loop to get data.
Instead of doing that, follow below way.
You should define relationship between Product and PriceList tables.
In Model Product.php
public function price_list(){
return $this->hasMany('PriceList','product_code');
}
In Model PriceList.php
public function product(){
return $this->belongsTo('Product','product_code');
}
In Controller File
$products = Product::with('price_list')->get();
return View::make('your_view')->with('products', $products);
In View File (file must have extention .blade.php to use below syntax)
#foreach($products as $product)
{{ $product }}
#endforeach
Note:- Print $products in controller to check results.

I found a way with mysql,
SELECT
`products`.`id` AS `product_id`,
`products`.`product_code` AS `product_product_code`,
`products`.`purchase_type` AS `product_purchase_type`,
`products`.`quantity` AS `product_quantity`,
`price_list`.`product_code` AS `price_list_product_code`,
`price_list`.`purchase_type` AS `price_list_purchase_type`,
`price_list`.`minimum` AS `price_list_minimum`,
`price_list`.`maximum` AS `price_list_maximum`,
`price_list`.`unit_price` AS `price_list_unit_price`
FROM
`products`
LEFT JOIN `price_list` ON `products`.`product_code` = `price_list`.`product_code`
AND `products`.`purchase_type` = price_list.purchase_type
AND `products`.`quantity` >= price_list.minimum
AND `products`.`quantity` <= price_list.maximum
ORDER BY
`products`.`id` ASC
http://sqlfiddle.com/#!9/5d6ac/2

Related

how to get order item sales report in laravel?

I'm trying to get last month order items report. Here is my table structure
orders
id - integer
amount- double
order_items (pivot table)
id - integer
order_id - foreign
item_id - foreign
quantity
price
Here is my Item model
public function orders()
{
return $this->belongsToMany(Order::class, 'order_items', 'item_id', 'order_id')
->withPivot('quantity', 'price');
}
Here is my Order model
public function items()
{
return $this->belongsToMany(Item::class, 'order_items', 'order_id', 'item_id')
->withPivot('quantity', 'price')
->withTimestamps();
}
Here is my controller where I got last month's all orders
$orders = Order::with('items')
->whereMonth('created_at', '=', Carbon::now()->subMonth()->month)
->get();
After foreach loop in blade,
#foreach ($orders as $order)
<ul>
#foreach($order->items as $item)
<li>
{{ $item->name }}, {{ $item->pivot->quantity }}, {{ $item->pivot->price }}
</li>
#endforeach
</ul>
<hr>
#endforeach
I'm getting data like this
Item Name Quantity Price
Item A 20 600
Item A 15 400
Item A 5 200
Item B 5 100
Item B 5 100
But I don't want to show same item in this row, I want to show like this
Item Name Quantity Price
Item A 40 1200
Item B 10 200
How to sum quantity and price if item is same?
I think this could solve your problem:
$orders = Order::with('items')
->whereMonth('created_at', '=', Carbon::now()->subMonth()->month)
->get();
$orders->transform(function($order){
$itemsArray = [];
$order['items'] = $order['items']->groupBy('name')->map(function ($item) use ($itemsArray){
array_push($itemsArray, [
'name' => $item[0]['name'],
'qty' => $item->sum('qty'),
'price' => $item->sum('price')
]);
return $itemsArray[0];
})->values();
return $order;
});

How to use whereNotNull in controller while using with (' Many-to-many relationships') to show on the blade view?

I try to show the 'categories' from Many to Many relationships and use
whereNotNull at the same time but when I use whereNotNull to check that the users have approved then the categories from the relationships are disappeared in the blade view.
If I have not put whereNotNull. The list of users who are vendors still appears (approved and waiting for approval both of them are showing in the view with the category relationships). When I use whereNotNull the blade view will show only approved users without category relationships.
The explanation pictures.
When use whereNotNull click!
When not use whereNotNull click!
This is the view that I want to show the categories from the relationships.
#if(count($user->categories) > 0)
#foreach ($user->categories as $category)
<span class="badge badge-pill badge-info p-2 m-1">{{ $category->name }}</span>
#endforeach
#endif
The UserController
public function index()
{
$users = User::with('categories') // Eager loading
->where('role', 'vendor')
->whereNotNull('approved_at') //If I put whereNotNull the categories will now showing
->get(); but If I delete it, the relationships will appear in the
view again.
return view('admin.vendorDashboard')
->with('categories', Categories::all())
->with('users', $users);
}
}
User Model
public function categories()
{
return $this->BelongsToMany(Categories::class);
}
Categories Model
public function users()
{
return $this->BelongsToMany(User::class);
}
User Table
Id
User
Approved_at
Role
1
A
2020-12-24 18:40:59
Vendor
2
B
2020-12-24 18:39:59
Vendor
Category Table
Id
Category
1
C
2
D
user_category table
Id
user_id
category_id
1
1
1
2
1
2
Thank you in advance.
p.s. sorry for my bad English.

Laravel sort child relationship collection

I have the following 3 tables:
Movie
- id
Series
- id
- status_id
- movie_id
Status
- id
- order_number
this is the code on the controller:
$movie = Movie::where('slug', $slug)->with(['series'])->first();
this is the code for view:
#foreach ($movie->series as $series)
{{ $series->name}}
#endforeach
how to sort $movie->series based on status->order_number? if it can be written on the model, so every order is only written once the controller?
is there a code that I can use for example like this:
$movie->series->sortBy('status.order_number');
Yes, but you will need to join status with series:
$movie = Movie::where('slug', $slug)->with([
'series' => function ($query) {
// Subquery on `series` table
return $query
// select whatever you'll need
->select('series.*')
// join with status
->join('status', 'series.status_id', '=', 'status.id')
// order by order number
->orderBy('status.order_number')
// * you can drop this if you select all the fields you need and use those
->with('status');
},
])->first();
Edit this ^ method will sort on SQL level, but you could also do this with collections:
#foreach ($movie->series->sortBy('status.order_number') as $series)
{{ $series->name}}
#endforeach
In that case also add .status to your with to avoid n + 1 problem: ->with(['series.status'])
The reason your attempt didn't work is because ->sortBy(..) doesn't mutate the collection, it just returns a new sorted one. This would work:
$movie->series = $movie->series->sortBy('status.order_number');

Laravel 5.7 - "orderBy" Not Sorting Results

I have three models related through hasMany relationship: Course/Lesson/Article - A Course hasMany Lessons which hasMany Articles.
I have an int column in my articles table named pos (short for position) and I want to order the articles by it.
I am using the following query in my CoursesController, but it's not sorting the articles by this pos attribute:
Code:
public function show(Course $course, Lesson $lessons, Article $articles)
{
$articles = $lesson->articles()->orderBy('pos', 'asc')->get();
return view('users.courses.show', compact('course', 'lessons', 'articles'));
}
I'm using a foreach loop in blade:
#foreach($lesson->articles as $article)
{{ $article->title }}
#endforeach
Any help would be appreciated!
Laravel debugbar shows the following result:
select * from articles where slug = 'this-is-article-one' limit 1
13.27ms\vendor\laravel\framework\src\Illuminate\Routing\ImplicitRouteBinding.php:35
select * from articles where lesson_id = 1 and pos < 2 order by pos desc limit 1 660μs\app\Http\Controllers\Users\ArticlesController.php:55
select * from articles where lesson_id = 1 and pos > 2 order by pos asc limit 1 520μs \app\Http\Controllers\Users\ArticlesController.php:59
select * from courses where courses.id = 2 limit 1 610μs view::users.articles.show:7
select * from lessons where lessons.id = 1 limit 1 530μs
view::users.articles.show:8
select * from articles where articles.lesson_id = 1 and articles.lesson_id is not null
When you call $lesson->articles again in the show view, basically you make a new DB call. To get the correct values, use the variable $articles:
#foreach($articles as $article)
{{ $article->title }}
#endforeach
If you want to continue using your lesson object in your view, use sortBy:
#foreach($lesson->articles->sortBy('pos') as $article)
{{ $article->title }}
#endforeach
I will suggest to extend you relationship with orderBy()
<?php
public function articles(){
return $this->hasMany('App\Articles')->orderBy('pos','asc');
}

Laravel relationship problems

I've got 4 tables:
My relationships should work like this:
Items can only have one size, color and category.
This should be working but it's really not. The query generated returns wrong results.
Here are my model files:
<?php
class Shop_Item extends Eloquent
{
public static $table = 'items';
public static $timestamps = false;
public function scategory() {
return $this->has_one('Shop_Category','id');
}
public function ssize() {
return $this->has_one('Shop_Size','id');
}
public function scolor() {
return $this->has_one('Shop_Color','id');
}
}
The rest of the model files for the remaining tables are the same (except table name and model name).
<?php
class Shop_Category extends Eloquent
{
public static $table = 'item_categories';
public static $timestamps = false;
}
So when I try to access the extra values (size->name, color->name, category->name), I get wrong results.
I have two test records in my database:
Item 1 and Item 2 with different color, size and category.
Item 1 is blue and have the size of M,
Item 2 is green and have the size of XL, but not in the returned query. Which shows me, that Item 2 is red and have the size of S.
Controller:
<?php
class Admin_Shop_Controller extends Base_Controller {
public function action_index() {
$items = Shop_item::order_by('name')->paginate(10,array('id','name','price','sex','visible','color','size','category'));
return View::make('admin.shop.index')->with('items', $items);
}
View:
#forelse($items->results as $i)
{{ $i->name }}
{{ $i->price }}
{{ $i->sex }}
{{ $i->scategory->name }}
{{ $i->scolor->name }}
{{ $i->ssize->name }}
Edit
Delete
#empty
There are no items in the shop.
#endforelse
Queries generated:
0.23ms
SELECT COUNT(`id`) AS `aggregate` FROM `items`
0.28ms
SELECT `id`, `name`, `price`, `sex`, `visible`, `color`, `size`, `category` FROM `items` ORDER BY `name` ASC LIMIT 10 OFFSET 0
0.25ms
SELECT * FROM `item_categories` WHERE `id` IN ('1', '2')
0.21ms
SELECT * FROM `item_sizes` WHERE `id` IN ('1', '2')
0.36ms
SELECT * FROM `item_colors` WHERE `id` IN ('1', '2')
Note that in the view if I access these values from the other table like this:
{{ Shop_Color::find($i->color)->name }}
It gets me the right result, but I really don't want to query the database n+3 times because of this. Any suggestions what am I doing wrong?
Edit: Still no luck. :( I've done the changes you listed, experimented with them but this thing still not working. Current error is :
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'where clause'
SQL: SELECT * FROM `item_colors` WHERE `id` IN (?)
Bindings: array (
0 => 0,
)
I don't know why it looks for an id, I've changed all references to the child tables and renamed columns appropriately. :(
Number of queries !== Performance. In your case, you're doing nearly-atomic queries (the where id in are all done vs. primary indices, which will make them fast - as fast as a JOIN if you exclude the returning-result process time). If you nevertheless want to solve the n+1 query problem, Eloquent has a solution for this: eager-loading.
Instead of this:
Shop_item::order_by('name')
Use this:
Shop_item::with(array("scategory", "ssize", "scolor"))->order_by('name')
You should see only one query by using this. The doc for this feature is at: http://laravel.com/docs/database/eloquent#eager
There are quite a few things going on here.
I would name your reference columns thing_id... so that's color_id, size_id and category_id. This will allow you to set up relationships named 'color', 'size' and 'category', instead of 'sthing'.
You need belongs_to() instead of has_one(). Laravel assumes that the ID will be like thing_id, so if you've updated as 1 above then you can update your references like $this->belongs_to('Shop_Size'). If not, then you should use the reference column here, like $this->belongs_to('Shop_Size', 'size').
When you use Eloquent models it's best not to restrict the columns - you might have logic in your model that depends on them all being there.
You can use eager loading to improve the queries, but the way Eloquent works it will still need a query per relationship. Have a look at this line for action_index()
$items = Shop_Item::with(array('color', 'size', 'category'))
->order_by('name')->paginate(10);
After all of the edits above you will be able to write code like this in your view...
#forelse ($items->results as $item)
<p>Color: {{ $item->color->name }}</p>
#else
<p class="no-results">There are no items in the shop.</p>
#endforelse
Most of this is covered in the Eloquent docs, particularly Relationships and Eager Loading.

Categories