laravel slow query issue optimization - php

I want to optimize my query as it is taking long to run with current eloquents. I have two table, toys and product.
From each product one is reserved as sample of toy if not than it has to be updated as sample by the query so what i'm doing right now is below.
$toywithsample=product::select('toyid')->groupBy('toyid')->where('sample','yes')->get();
Above code is to get id of all the product with which have their one sample from in its product
$toywithoutsamples=toy::select('id')->whereNotIn('id',$toywithsample)->get();
Above code is to get id of all product which have no sample toy in
foreach($toywithoutsamples as $toywithoutsample){
$product=product::where('toyid',$toywithoutsample->id)
->where('sample','sale')->limit(1)
->update(['sample'=>'yes']);
}
Below is table structure
toy table
id,name,
product
id, toyid,sample

$toys_ids_with_sample = Product::where('sample', 'yes')->get()->pluck('toyId');
// get the products grouped by toyId.
$products = Product::whereNotIn('toyId', $toys_ids_with_sample)->where('sample', 'sale')->get()->groupBy('toyId');
// get the product ids whose sample field you want to change to
// yes.
$update_product_ids = [];
foreach($products as $toyId => $products){
// We will only pick the first one, as we have to change just 1.
array_push($update_product_ids, $products->first()->id);
}
Product::whereIn('id', $update_product_ids)->update(['sample' => 'yes']);
This reduces the total number of queries.

Related

Foreach loop only display one item for specific rows on CSV export

I'm pulling data from Magento and am exporting it to a CSV, the data includes order number, items, sub total and more. The export splits the rows by 'product' and groups them by the 'order ID' which is causing things like the 'sub total' to be duplicated. Is there a way to stop this. It's easier to explain in the code, please see below - the items with ONLY SHOW ONCE should only be generated on the first row of that order.
foreach ($order->getAllVisibleItems() as $orderItem) {
$orderData = [
(new \DateTime($order->getCreatedAt()))->format('Y-m-d'),
$order->getIncrementId(),
($invoice->getCreatedAt())?(new \DateTime($invoice->getCreatedAt()))->format('Y-m-d'):null, //Invoice Date.
$invoice->getIncrementId(), //Invoice No.
$billingAddress->getName(), //Billing Name
$shippingAddress->getCountryId(), //Shipping Country
$this->formatPrice($order->getSubTotal() + $order->getDiscountAmount()),//Subtotal ***ONLY SHOW ONCE***
$this->formatPrice($order->getShippingAmount()), //Shipping
$this->formatPrice($order->getTaxAmount()), //Taxes
$this->formatPrice($order->getGrandtotal()), //Total
$this->formatPrice($order->getDiscountAmount()), //Discount Amount
$this->formatPrice($order->getTotalRefunded()), //Refunded Amount
$orderItem->getSku(), //Lineitem sku
$orderItem->getName(), //Lineitem name
round($orderItem->getQtyOrdered()),//Lineitem quantity
$this->formatPrice($order->getSubTotal() + $order->getDiscountAmount()),//Lineitem price
$order->getPayment()->getMethodInstance()->getTitle(),//Payment Method
$invoice->getTransactionId(),//'Payment Reference'
];
$orderCsv = implode(",", $orderData);
$file->write($orderCsv . PHP_EOL);
}
Obviously, since the rows are split by the product name some items need to be shown on all rows such as the 'product price'. This is obviously only part of the code but I though it was too long to paste it all in.

Load product information by comparing custom attribute in Magento

I am working on some scripts to automate some things inside our webshop.
I have looked through many forums and questions.
Now I almost have finished my script but there is a small thing that doesn't work but I can not think of what I am doing wrong.
What the goal of this script is, is to get products that has the same attribute value as the values in an array (pulled from DB).
So here is my code:
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once('../app/Mage.php');
require_once('db.php');
Mage::app();
$db = db_connection();
$collection = Mage::getModel('catalog/product')->getCollection();
$collection->addAttributeToSelect('ean');
$getean = $db->prepare('SELECT ean_l FROM mytable');
$getean->execute();
$allean = $getean->fetchAll();
foreach($allean as $ean) {
$collection->addFieldToFilter(array(
array('attribute'=>'ean','eq'=>'' . $ean['ean_l'] . ''),
));
echo 'ean_l: ' . $ean['ean_l'] . '<br>';
foreach ($collection as $product) {
echo $product['entity_id'];
}
}
So here's how it works:
We select an attribute (ean).
We get a list of all ean numbers from the database.
We loop through the list and compare any product with the ean number.
Then we loop through the collection and get the id of the corresponding product.
Yet, all $product['entity_id']'s are 273. It is correct that the entity_id is 273, but there is also product 274 with a corresponding ean number.
Here is the result from the script (it's alot more):
So why is this? Because in my reasoning, it changes the ean_l every loop and it equalizes it with the attribute values.
And then it should change the collection, right?
So shouldn't it at least show 274 at some point?
This question is not especially for Magento programmers, but other programmers can help too, so I figured to post it on SO.
Magento comes with powerfull filtering and queries into collections. If that doesn't satisfy, you can always extend with custom queries into getSelect function. Some info here.
Using addFieldToFilter into that foreach will filter the remaining values after another iterated filtering. So it's not good.
$allean = array("of", "ean", "values", "needed", "for", "filtering");
$collection = Mage::getModel('catalog/product')->getCollection();
$collection->addAttributeToSelect('ean');
//addAttributeToFilter for EAV collections
$collection->addAttributeToFilter('ean', array('in' => $allean)); //not addFieldToFilter
$collection->getColumnValues('entity_id');
var_dump($collection); //will output an array of product_ids
Alternative, if you want to group by ean values you should remove getColumnValues and run group command.
You can find additional info here.
Or you can just remove getColumnValues, start a foreach($collection as $product) and group manually or do what you want with those filtered products.

Symfony2 creating objects with session keys and values

BACKGROUND
I have two tables in my database, CampaignList and CampaignProduct. The logic is simple, if the user successfuly purchases products I have to create one new CampaignList and CampaignProducts based for the number of products bought. These two tables will be mapped together in the future, right now I am just trying to insert them correctly.
So for example if a user successfuly buys 3 products one new table is inserted in CampaignList and 3 new tables is CampaignProduct.
Now the products are storred in session like this:
11 => 2
29 => 1
The key is the id of the product and the value is the quantity. So this session has 3 products, two products with the id of 11 and one products with the id of 29. Now for the problem and the code.
THE PROBLEM
The inserts are working correctly, except one. I need to save the quantity of the product too in the database. But this way that I am creating I dont think i can? Because I am creating tables in a different loop where the quantity is never iterated? Here is the code
THE CODE
if ($session->has('cart') && count($session->get('cart')) > 0) {
// if the session is good create the new campaign
$campaign = New CampaignList();
$campaign->setUserId($user->getId());
$campaign->setName('Karpedeal');
$campaign->setState(1);
$em->persist($campaign);
$em->flush();
foreach ($cart as $id => $quantity) {
// store the product ids in an array
$productIds[] = $id;
//get all the products based on the id array
$product = $em->getRepository('MpShopBundle:Product')->findById($productIds);
}
// for each new product create new CampaignProduct
foreach($product as $item){
$campaignProduct = New CampaignProduct();
$campaignProduct->setCampaignId($campaign->getId());
$campaignProduct->setProductId($item->getId());
$campaignProduct->setSellingPrice($item->getPrice());
$campaignProduct->setStock($item->getStockAvailable());
$campaignProduct->setReserved($quantity); // PROBLEM how to get the quantity from the session??
$em->persist($campaignProduct);
$em->flush();
}
THE OTHER WAY
The only way I think I can do is do everything in the first foreach loop, however that way I am getting an error when I try to get the ids of the products, because they are not objects, but arrays...
if ($session->has('cart') && count($session->get('cart')) > 0) {
// if the session is good create the new campaign
$campaign = New CampaignList();
$campaign->setUserId($user->getId());
$campaign->setName('Karpedeal');
$campaign->setState(1);
$em->persist($campaign);
$em->flush();
foreach ($cart as $id => $quantity) {
// store the product ids in an array
$productIds[] = $id;
//get all the products based on the id array
$product = $em->getRepository('MpShopBundle:Product')->findById($productIds);
$campaignProduct = New CampaignProduct();
$campaignProduct->setCampaignId($campaign->getId());
$campaignProduct->setProductId($product->getId()); // the error here because $product is an array not object
$campaignProduct->setSellingPrice($item->getPrice());
$campaignProduct->setStock($product->getStockAvailable());
$campaignProduct->setReserved($quantity); // PROBLEM how to get the quantity from the session??
$em->persist($campaignProduct);
$em->flush();
}
Any ideas?
By looking at your code I guess your second answer is best, but instead of getting all products with an array of product ids, just get one product every run which results in your product to be an instance of MpShopBundle:Product.
tl;dr
changing this
$product = $em->getRepository('MpShopBundle:Product')->findById($productIds);
into this
$product = $em->getRepository('MpShopBundle:Product')->find($id);
Should work

ORM Mapping two tables with PHP

Current situation
I have two tables in my database, one for posts, and one for ratings. These are linked with a relation in the MySQL so that one post may have 0, 1 or multiple ratings, but one rating can only be applied to one post.
When I fetch a list of posts, I also want to get ratings, but without having to make a separate call to the database for each post in the foreach loop.
To do this I have attempted to use an SQL query to fetch all posts with a LEFT JOIN on ratings so that it will return a result like this:
statusId|statusBody|rating
-----------------------------
1, post1, 0
1, post1, 1
2, post2, 0
3, post3, 1
3, post3, 1
The SQL works fine, and I get the data I ask for.
Ideally what I am trying to achieve now is to turn this table into a collection of objects, with each object storing the post information as well as a value depending on it's total ratings.
After using PDO to return the data result, this is the code I am using to map the data:
Code Logic
The logic of my code goes like this:
Get all statuses joined with ratings table
Create empty output array
Loop through PDO result
{
Create loop specific temp array
Push first row of result into temp array
Remove row from PDO result
Loop through PDO result for objects with matching statusId
{
If row matches statusId, add to temp buffer and remove from PDO result
}
Take first row of buffer and create status object
Loop through objects in temp array to calculate ratings and add onto above status object
Clear temp buffer
Add status object to output array
}
return output array
Actual Code
try
{
$result = $pdo->query($sql);
//if($result == false) return false;
$statuses = $result->fetchAll(PDO::FETCH_CLASS, 'status');
}
catch (PDOException $e)
{
return FALSE;
}
if (!$result) {
return FALSE;
}
//create empty output array to be filled up
$status_output = array();
//loop through all status
foreach($statuses as $s1key => $s1value)
{
//initialise temporary array;
$status_temp_buffer = array();
//create temp array for storing status with same ID in and add first row
array_push($status_temp_buffer, $s1value);
//remove from primary array
unset($statuses[$s1key]);
//loop through array for matching entries
foreach($statuses as $s2key => $s2value)
{
//if statusId matches original, add to array;
if($s2value->statusId == $s1value->statusId)
{
//add status to temp array
array_push($status_temp_buffer, $s2value);
//remove from primary array
unset($statuses[$s2key]);
}
//stop foreach if statusId can no longer be found
break;
}
//create new status object from data;
$statObj = $status_temp_buffer[0];
//loop through temp array to get all ratings
foreach($status_temp_buffer as $sr)
{
//check if status has a rating
if($sr->rating != NULL)
{
//if rating is positive...
if($sr->rating == 1)
{
//add one point to positive ratings
$statObj->totalPositiveRatings++;
}
//regardless add one point to total ratings
$statObj->totalAllRatings++;
}
}
//clear temporary array
$status_temp_buffer = NULL;
//add object to output array
array_push($status_output, $statObj);
}
Problem
The problem I am coming up against with this code is that although the ratings are fine, and it correctly calculates the ratings total for each post, it still shows duplicates where a post has more than one rating.
Any help with this would be greatly appreciated,
Thanks
As i understood it, the goal is to get the total rating of each Post entry. Instead of manually looping over each and every rating, there are two other path you could take:
compute the total in the query:
SELECT SUM(rating) AS total , .. FROM Posts LEFT JOIN .... GROUP BY statusID
You will receive a list of Post entries, each already with total rating calculated. This is a very good solution if you have a lot of writes to to the Ratings table, and much less reads.
the other way is to break the table normalization, but to increase read performance. What you would have to do is to add another column in the Posts table: total_rating. And have an TRIGGER on INSERT in the Ratings table, which changes the Posts.total_rating accordingly.
This way has a benefit of simplifying the request of Posts. At the same time Ratings table can now be use to ensure that total_rating has been calculated correctly, or to recalculate the value, if there are some large changes in the ratings: like banning of user, which results in removing all ratings made by this user.

How separate search results by category? MySQL + PHP

Hi!
I didn't get any code that worked. Of course I could have use then wrongly because I'm a beginner. Some told me to use MySQL subqueries other told me to use PHP foreach achieve it.
What I want is to show the search results of a keyword separated by groups of categories, something like that:
Search results for Item, 3 itens in 2 categories:
Category 1:
Item 1
Item 10
Category 2:
Item 1003
Can someone explain me it as simple as possible.
Thanks n advance!
I use a single request which return name of category for each item and I use PHP to display it
<?php
$cat;
while($result = $statement->fetch()) {
if($result['cat'] !== $cat) {
$cat = $result['cat'];
/* display cat */
}
/* display items */
}
?>

Categories