Laravel eager loading use parents column value - php

Here is my scenario, I have 2 table Product and Rack. They have many to many relationship, so there is a pivot table ( History Class ) to connect them .
Tables
This is our Products table:
id
Name
1
Product A
2
Product B
3
Product C
Rack table:
id
code
1
X1
2
Y2
3
Z3
History table:
product_id
rack_id
historyable_id
historyable_type
quantity
Product A
X1
1
Receive
10
Product A
Y1
1
Receive
5
Product A
X1
1
Delivery
2
Product B
X1
2
Receive
10
I want to get list like this:
product_id
rack_id
historyable_id
historyable_type
quantity
Product A
X1
1
Receive
8
Product A
Y1
1
Receive
5
Product B
X1
2
Receive
10
If type receive is 'debit' and delivery is 'credit' to calculate quantity, and i have two unique column in here product_id and rack_id.
Here is my method right now:
public function collection()
{
$products = Product::with('racks')->get();
$results = collect();
foreach ($products as $product) {
foreach ($product->racks as $rack) {
$rackWithHistory = $rack->load(['history' => function ($q) use ($product) {
return $q->where('product_id', $product->id)->with('histories:id');
}]);
$results->push([
'product_name' => $product->name,
'product_description' => $product->description,
'location' => $rack->code,
'quantity' => $rack->getQuantity($rackWithHistory->history)
]);
}
}
return $result;
}
It works but in large data (try in 2000rows) that takes a very long time, and always touch
php - Allowed memory size of 134217728 bytes exhausted can you guys give me some tips so it can load faster?

You could try chunking the data
Product::with('racks')->chunk(100, function ($products) use($results) {
foreach ($products as $product) {
foreach ($product->racks as $rack) {
$rackWithHistory = $rack->load(['history' => function ($q) use ($product) {
return $q->where('product_id', $product->id)->with('histories:id');
}]);
$results->push([
'product_name' => $product->name,
'product_description' => $product->description,
'location' => $rack->code,
'quantity' => $rack->getQuantity($rackWithHistory->history)
]);
}
}
});
When you chunk data, you only take a x amount of items at a time. In your case 100 items at a time.
https://laravel.com/docs/8.x/queries#chunking-results

Related

Multiple row sum and update

I want to update multiple rows while all of them will be summed up with their previous records. For Example, There are 2 products "a" and "b" in a table called "products", they have a column called "quantity". Let's say the product "a" has a quantity of 20 and product "b" has a quantity of 25. Now I am inserting both products' new quantities (the new quantity for product "a" is 5 and 4 for product "b"). The query should update both products' quantity before the insertion, like the quantity of product "a" will be 25 and product "b" will be 29. My codes are :
foreach ($request->product_id as $key => $value) {
$input['quantity'] = $request->product_quantity[$key];
$update_stock = Product::update(['id'=>$request->product_id[$key]],
$input);
}
You can use the increment here.
Suppose the request array will be,
$productsWithQty = [
1 => 5,
2 => 4
];
Where the 1 and 2 is your product ids, product A and B respectively and 5 and 4 is quantities. So query will be,
foreach ($productsWithQty as $product => $qty) {
Product::findOrFail($product)->increment('quantity', $qty);
}
Try this and let me know the results.

Laravel - Set collection data from another collection

I have two tables: products and colors.
Products
id | product_name | color_id
----------------------------
1 | Product 1 | 1
2 | Product 2 | 2
Colors
id | name
---------
1 | blue
2 | silver
3 | green
And i have collection:
$product = Product::all();
And i want to have another collection from color table with colors which exists in product collection. So i want to see colors: blue (product 1) and silver (product 2) without green. Is it possible to get something like this? I think about relationship but i'm not sure how to do it. Thanks.
If you want take colors from colors table that are assigned to any product, then you can do like this:
$products = Product::all();
$assigned_color_ids = $products->pluck('id')->toArray();
$colors = Color::whereIn('id', $assigned_color_ids)->get();
For your given table, the query will be whereIn('id', [1, 2]) because color with id 3 is not used in products table
maybe can try this,
in your COlors model define a relation to products
/**
* Get the products for the color.
*/
public function products()
{
return $this->hasMany('App\Products','color_id');
}
and you can access to all products from color model,
$colorswithproducts = Colors::whereHas('products')->get();
dd($colorswithproducts);
1 => [
'name' => 'blue'
'products' =>
[
"id" : "1",
'name' => ....
]
]

How to minus from several MySQL fields a certain value?

I am writing products stock script. I have a MySQL table "products_stock":
id zid pid amount
1 123 321 1
2 124 321 5
4 124 566 7
3 125 321 10
So, total amount of product id = 321 in stock is 16. Somebody makes order with pid=321 and qty = 7. I need some php-function, which will minus 7 from column amount starting from the first zid, and update records in table products_stock so that it lookes after like this:
id zid pid amount
1 123 321 0
2 124 321 0
4 124 566 7
3 125 321 9
I am stucked from this point.
Thank you for answers!
I don't use codeigniter but going through the documentation on how to perform a select operation and batch update. The main issue is getting your logic right... You select all the product entry for that particular item and you iterate through and subtract the amount of each from the ordered product.
<?php
//your order
$orders = [
['id' => 321, 'qty' => 7],
['id' => 501, 'qty' => 20],
];
$data = [];
foreach($orders as $order) {
$items = $this->db->where('pid', $order['id']);
$order_qty = $order['qty'];
foreach($items as $item){
$item_qty = $item->amount - $order_qty;
if ($item_qty <= 0) {
// set amount to 0 because it is less than order quantity
$data[] = ['id' => $item->id, 'amount' => 0];
// Subtract amount from order quantity to get the remaining order
$order_qty = $order_qty - $item->amount;
} else {
//If amount is more than order quantity set to calculated amount
$data[] = ['id' => $item->id, 'amount' => $item_qty];
// update order to 0
$order_qty = 0;
}
//exit if order quantity is 0
if ($order_qty === 0 ){
break;
}
}
}
$this->db->update_batch('product_stock', $data, pid');
You can do so by selecting all the relevent rows from the products_stock table and subtract/update the values one by one in loop until your required quantity gets over.
Example code
// These are the inputs you will be getting.
$pid = 321;
$qty = 7;
// Fetch all the relevent rows using the below query statement.
$select_sql = "select * from products_stock where pid = {$pid} and amount > 0";
// This will return the below array.
$data = [
[
'id' => 1,
'zid' => 123,
'pid' => 321,
'amount' => 1,
],
[
'id' => 1,
'zid' => 124,
'pid' => 321,
'amount' => 5,
],
[
'id' => 1,
'zid' => 125,
'pid' => 321,
'amount' => 10,
],
];
$update_data = [];
// You can then loop through your rows to perform your logic
foreach ($data as $row) {
// Exit loop if qty is 0
if ($qty == 0) {
break;
}
$id = $row['id'];
$amount = $row['amount'];
// Subtract the required amount from qty.
$qty -= $amount;
$amount = 0;
// If extra qty was consumed, add back to the amount.
if ($qty < 0) {
$amount =+ ($qty * -1);
$qty = 0;
}
// Update you data here or the best practice would be to avoid sql in a loop and do a bulk update.
// You can do this based on your requirement.
// $update_sql = "update products_stock set amount = {$amount} where id = {$id}";
// Prepare date for bulk update
$update_data []= [
'id' => $id,
'amount' => $amount,
];
echo "Qty {$qty} | Amt {$amount} \n";
echo "--------\n";
}
// Bulk update all your rows
if (count($update_data) > 0) {
$this->db->update_batch('products_stock', $update_data, 'id');
}
echo "\n";
echo "Balance Qty ". $qty . "\n";
Output
Qty 6 | Amt 0
--------
Qty 1 | Amt 0
--------
Qty 0 | Amt 9
--------
Balance Qty 0
Refer https://eval.in/920847 for output.
You can try running the same code with different qty.
This is the rough logic for your use case which will work as expected. Still there may be better ways to do this in an optimal way.
Glad if it helps you.

Getting all items on Amazon Product API

Amazon's Product API limits us to get only 10 items per page, and only 10 pages at a certain query.
I have developed a code that would almost get all items;
first, I have supplied a params that looks like this:
$item_params = [
"Service" => "AWSECommerceService",
"Operation" => "ItemSearch",
"AWSAccessKeyId" => env('AWS_ACCESS_KEY_ID'),
"AssociateTag" => env('AWS_ASSOCIATE_TAG_ID'),
"SearchIndex" => "HomeGarden",
"ResponseGroup" => "ItemAttributes,SalesRank,Offers",
"Sort" => "-price",
"BrowseNode" => $item_params['BrowseNode'],
"MaximumPrice" => $max_price,
"MinimumPrice" => "0"
];
then, the code will get all items under that browse node (category), SORTED BY PRICE (desc) also by specifying the MAX and MIN Price of the items to limit the search.
the pseudo-code (original code is too long)
function getProducts($item_params, $max_price = null){
$products = //request to amazon
foreach ($product as $key=>$value){
//add product to db
}
// if the total number of results on the query is not equal to zero, continue looping
if (!$products->totalResults() == 0){
$product = //get the first lowest priced item on the db
$this->getProducts($item_params, $product->price);
}
}
however I am experiencing this scenario :
Sample request output (assuming all items from amazon):
ASIN(unique id) | Price
1 | 201
2 | 194
3 | 195
.
.
n | 33
n+1 | 33
n+2 | 33
.
n+120 | 33
n+121 | 34
n+122 | 35
wherein the products from n to n+120 are equal. This will create an infinite loop to my getProducts function. How can I avoid this? Knowing that only 10 items are returned on each request and only 10 pages.
How can I avoid this?
I don't think you can with just using price. You have to divide your search into multiple sub-searches by using additional keywords. For example, if you're searching for "laptop", instead do searches on "laptop asus", "laptop dell", etc.
You can also filter on Browse node IDs, so if your results come from multiple browse nodes, you can do two or more searches.
Add the ItemPage parameter and increment it in a loop. You should be able to get up to 100 unique ASINs (10 pages of 10 products per page).
$page = 1;
while($page <= 10) {
$item_params = [
"Service" => "AWSECommerceService",
"Operation" => "ItemSearch",
"AWSAccessKeyId" => env('AWS_ACCESS_KEY_ID'),
"AssociateTag" => env('AWS_ASSOCIATE_TAG_ID'),
"SearchIndex" => "HomeGarden",
"ResponseGroup" => "ItemAttributes,SalesRank,Offers",
"Sort" => "-price",
"BrowseNode" => $item_params['BrowseNode'],
"MaximumPrice" => $max_price,
"MinimumPrice" => "0",
"ItemPage" => $page
];
// execute query and save data
//increment page number
$page++;
}

Laravel - Order by pivot value in Many to Many table relationship

My application (Laravel 5.0) has a Products table and a Formats table. There's a manyToMany relationship between these two table (format_product). One product can be sold in many formats. Each relationship has a specific price so I have added a "price" column in the format_product table.
Now I'm trying to sort the products by price (being the cheapest format-price of each product the reference value).
One more thing, I need to paginate the results.
class Product extends Model {
public function formats()
{
return $this->belongsToMany('App\Format')->withPivot('price')->orderBy('pivot_price', 'asc');
}
}
class Format extends Model {
public function products()
{
return $this->belongsToMany('App\Product')->withPivot('price');
}
}
This is the format_product_pivot:
Schema::create('format_product', function(Blueprint $table) {
$table->integer('format_id')->unsigned()->index();
$table->foreign('format_id')->references('id')->on('formats')->onDelete('cascade');
$table->integer('product_id')->unsigned()->index();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->decimal('price', 8, 2);
});
In example, having this values:
Product A - Format 1 = 15€
Product A - Format 2 = 10€
Product B - Format 1 = 8€
Product B - Format 2 = 20€
Product C - Format 3 = 5€
Product C - Format 1 = 2€
I want this result:
Product C - 1 ( 2€)
Product B - 1 ( 8€)
Product A - 2 (10€)
Ok so I don't typically put orderBy() in my model, but it shouldn't be too much of an issue. You're going to have to use a join to get the results you want.
You can use the following query in your controller:
public function index() {
$products = Product::join('format_product', 'format_product.product_id', '=', 'products.id')
->select('products.id', 'format_product.format_id', 'format_product.price')
->orderBy('format_product.price', 'asc')
->paginate(25)
->get();
}
The reason you can't sort products by a relationship is the same reason you can't sort a multidimensional array by an inner array.
For example:
$array = [
'key1' => [
'anotherKey' => 'some value'
],
'key2' => [
'anotherKey' => 'some other value',
'anotherKey2' => 'some other value2'
],
'key3' => [
'anotherKey' => 'yet another value'
]
]
You can't sort this array by anotherKey. You have to use a join.
Hope this helps.

Categories