I want to update the stock quantities of my products in the DB after a purchase.
My code already works fine, but I want to know if there is a best way to do, with only one SQL statement?
// All the product in the member's cart
if ($stmt = $conn->prepare("SELECT product_id, quantity FROM tbl_cart WHERE member_id = ?")) {
$stmt->bind_param("i", $memberId);
$stmt->execute();
$result = $stmt->get_result();
$stmt->close();
$cartItem = $result->fetch_all(MYSQLI_ASSOC);
// Set the quantity after purchase
foreach ($cartItem as $key => $item) {
$stmt = $conn->prepare("UPDATE tbl_product SET stock = stock-? WHERE id = ?");
$stmt->bind_param("ii", $item['quantity'], $item['product_id']);
$stmt->execute();
$stmt->close();
}
}
I don't know if this is better way but you may try this Sql statement.
UPDATE tbl_product
SET stock = stock - ( SELECT quantity FROM tbl_cart
WHERE product_id = tbl_product.id AND member_id = ? )
WHERE id IN ( SELECT product_id FROM tbl_cart WHERE member_id = ? )
You can try using trigger function. Look here: mysql after insert trigger which updates another table's column
You could update other column when order is placed.
In my opinion, the trigger function is not the best option, because it will move logic to other place.
But it is one of the alternative, that you ask for.
That's the solution I found. I think it's just more clean.
$stmt = $conn->prepare("UPDATE tbl_product AS p
JOIN tbl_cart AS c
ON p.id = c.product_id
SET p.stock = p.stock-c.quantity
WHERE c.member_id = ?");
$stmt->bind_param("i", $memberId);
$stmt->execute();
$stmt->close();
Thank you for your help.
Related
Hi guys I have this part of a code:
foreach($cartItems as $item){
$sql .= "INSERT INTO order_items (order_id, product_id, quantity) VALUES ('".$orderID."', '".$item['id']."', '".$item['qty']."');";
$newstock = $item['stock']-$item['qty'];
$sql1 .= "UPDATE product SET stock='".$newstock."' WHERE ID='".$item['id']."';";
}
// insert order items into database
$insertOrderItems = $conn->multi_query($sql);
$updateProductQty = $conn->multi_query($sql1);
and I am having problem about updating a table "product".. the first query is 1 and is ok, but sql1 output is OK but nothing happens, I tried to update this query manually in phpmyadmin and it works there.. Do you see anything what I dont? Thanks
edit: so it should look more like this?
$conn->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
foreach($cartItems as $item) {
$newstock = $item['stock']-$item['qty'];
$stmt = $conn->prepare('update product set stock = ? where ID = ?');
$stmt->bind_param('ii',$newstock,$item['id']);
$stmt->execute();
}
$conn->commit();
$conn->close();
this?
Yesterday i decided to learn PDO and rewrite our server php to PDO.
The thing that jumped to my mind while rewriting the code is the need of repeated use of bindParam for the same parameters i already used.
Here is an example:
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh->beginTransaction();
$stmt = $dbh->prepare("INSERT INTO Products(productID,numOfLikes) VALUES (:productID,0) ON DUPLICATE KEY UPDATE productID = productID;");
$stmt->bindParam(":productID",$productID);
$stmt->execute();
if($customerID !== 0){
//*****Check, if customerID is in the Database, else add the customerID to the Database.
$stmt = $dbh->prepare("INSERT INTO Customers(customerID) VALUES (:customerID) ON DUPLICATE KEY UPDATE customerID = customerID;");
$stmt->bindParam(":customerID",$customerID);
$stmt->execute();
//*****if customerID and productID are NOT registered together ,then register and add +1 to productID numOfLikes
$stmt = $dbh->prepare("SELECT customerID, productID FROM CustomerProducts WHERE productID = :productID AND customerID = :customerID");
$stmt->bindParam(":productID",$productID);
$stmt->bindParam(":customerID",$customerID);
$stmt->execute();
if ($stmt->rowCount() == 0) {
//echo "added";
$stmt = $dbh->prepare("INSERT INTO CustomerProducts(customerID, productID) Values (:customerID,:productID)");
$stmt->bindParam(":customerID",$customerID);
$stmt->bindParam(":productID",$productID);
$stmt->execute();
$stmt = $dbh->prepare("UPDATE Products SET numOfLikes = numOfLikes + 1 WHERE productID = :productID");
$stmt->bindParam(":productID",$productID);
$stmt->execute();
}else {
//echo "removed";
$stmt = $dbh->prepare("DELETE FROM CustomerProducts WHERE productID = ".$productID." AND customerID = ".$customerID);
$stmt->bindParam(":customerID",$customerID);
$stmt->bindParam(":productID",$productID);
$stmt->execute();
$stmt = $dbh->prepare("UPDATE Products SET numOfLikes = numOfLikes - 1 WHERE productID = ".$productID);
$stmt->bindParam(":productID",$productID);
$stmt->execute();
}
}
$dbh->commit();
Is there a way to write it in "prettier way"?
Can you see any flows in that could. I would appreciate every help.
Note: this code will be for production use in the near future.
Yes there is...
You can supply bindParam as an array to the execute function...
Something like this:
$statement->execute([
':username'=> $username,
':password'=> $password
]);
It's using bindParam and execute in just one statement, and it looks cleaner in my opinion.
Yes, you can get around the repeated variables by defining mySql user variables like this:
$psVars = $dbh->prepare("SET #pid = :productID;");
$psVars->bindParam(':productID', $productID);
$psVars->execute();
Then, in subsequent statements, just use #pid instead of a bound parameter
The array showcasef holds 20 items per page. I do 3 different queries within the foreach loop, which is 60 queries (just for the loop, there's additional queries too).
<?php
foreach($showcasef as $itemf){
$sf_id = $itemf['sf_id'];
$sf_url = $itemf['sf_url'];
$sf_title = $itemf['sf_title'];
$sf_urltitle = post_slug($sf_title);
// Fetch number of favs
$stmt = $conn->prepare("SELECT COUNT(f_id) FROM favourites WHERE f_showcaseid=?");
$stmt->bind_param("i", $sf_id);
$stmt->execute();
$stmt->bind_result($numfFavs);
$stmt->fetch();
$stmt->close();
// Fetch class
$stmt = $conn->prepare("SELECT avg(r_class) FROM ranks WHERE r_showcaseid=?");
$stmt->bind_param("i", $sf_id);
$stmt->execute();
$stmt->bind_result($sf_class);
$stmt->fetch();
$stmt->close();
// Fetch number of classes
$stmt = $conn->prepare("SELECT COUNT(r_class) FROM ranks WHERE r_showcaseid=?");
$stmt->bind_param("i", $sf_id);
$stmt->execute();
$stmt->bind_result($numfClasses);
$stmt->fetch();
$stmt->close();
?>
Render HTML here
<?php } ?>
Will this be a severe performance issue, or are these particular queries relatively simple? If I keep the columns indexed, should it perform okay with millions of rows (potentially)? Or can the queries be optimized/simplified?
Here's how I get the showcasef:
$stmt = $conn->prepare("SELECT s_id,s_url,s_title FROM showcase WHERE s_userid=? ORDER BY s_date DESC LIMIT $skippingFactor, 20");
$stmt->bind_param("i", $u_id);
$stmt->execute();
$stmt->bind_result($sf_id,$sf_url,$sf_title);
while($stmt->fetch())
{
$showcasef[] = [
'sf_id' => $sf_id,
'sf_url' => $sf_url,
'sf_title' => $sf_title
];
}
$stmt->close();
A few suggestions here.
Reuse prepared statements
You are creating three prepared statements inside the loop. Why don't you create your statements only once, and then reuse them using multiple binds?
<?php
$stmt1 = $conn->prepare("SELECT COUNT(f_id) FROM favourites WHERE f_showcaseid=?");
$stmt1->bind_param("i", $sf_id);
$stmt1->bind_result($numfFavs);
$stmt2 = $conn->prepare("SELECT avg(r_class) FROM ranks WHERE r_showcaseid=?");
$stmt2->bind_param("i", $sf_id);
$stmt2->bind_result($sf_class);
$stmt3 = $conn->prepare("SELECT COUNT(r_class) FROM ranks WHERE r_showcaseid=?");
$stmt3->bind_param("i", $sf_id);
$stmt3->bind_result($numfClasses);
foreach($showcasef as $itemf) {
$sf_id = ...
$stmt1->execute();
$stmt1->fetch();
/* if the fetch succeedes then $numfFavs will contain the count */
$stmt2->execute();
...
$stmt3->execute();
..
}
$stmt1->close();
$stmt2->close();
$stmt3->close();
Use a single query to Count the rows and calculate the average
You can combine the second and third statement a single SQL query:
SELECT COUNT(r_class) AS cnt, AVG(r_class) AS average
FROM ranks
WHERE r_showcaseid=?
Use a single query instead a foreach loop
With the previous suggestions you can get better performances. But are you really sure you need a foreach loop?
If your IDs are returned by another query, instead of a foreach loop is better to use a subquery:
SELECT f_showcaseid, COUNT(f_id)
FROM favourites
WHERE f_showcaseid IN (SELECT id FROM ... WHERE ...)
GROUP BY f_showcaseid
or you can provide a list of IDs to the query:
SELECT f_showcaseid, COUNT(f_id)
FROM favourites
WHERE f_showcaseid IN (?,?,?,?,?)
GROUP BY f_showcaseid
(you can dynamically create the list of ? if the number of IDs is not fixed)
You could do this in a single query I think.
Something like the following:-
SELECT f_showcaseid, COUNT(f_id), avg(r_class), COUNT(r_class)
FROM ranks WHERE r_showcaseid IN (".implode(',', $showcasef).")
GROUP BY f_showcaseid
Of course, to use parameters you would need to do that a bit more elegantly:-
<?php
$stmt = $conn->prepare("SELECT f_showcaseid, COUNT(f_id), avg(r_class), COUNT(r_class)
FROM ranks WHERE r_showcaseid IN (".implode(',', str_split(str_repeat('?', count($showcasef)), 1)).")
GROUP BY f_showcaseid");
foreach($showcasef as $itemf)
{
$stmt->bind_param("i", $itemf['sf_id']);
}
$stmt->execute();
$stmt->bind_result($numfClasses);
$stmt->fetch();
$stmt->close();
?>
So i think i'm close to figuring this out but my query won't add the item from the "pending" table to the "items" table. can you guys help me out with this please. Also if i want it to delete after it gets added should i add the code below the INSERT INTO SELECT query? thanks
action.php:
$sql = "INSERT INTO items (photo,title,description, name) SELECT (photo,title,description, name) FROM pending";
$stmt = $conn->prepare($sql);
$stmt->execute();
Example for delete query after it takes the item from the "pending" into items:
$idToDelete = filter_var($_POST["recordToDelete"],FILTER_SANITIZE_NUMBER_INT);
//try deleting record using the record ID we received from POST
$sql = "DELETE FROM pending WHERE id = :id";
$stmt = $conn->prepare($sql);
$stmt->bindParam(':id', $idToDelete, PDO::PARAM_INT);
$stmt->execute();
I think you are leaving yourself open to mistakes doing it this way.
Consider what would happen if a new row is added to the pending queue after you have issued the INSERT SELECT but before you have started your delete.
I think you need to do this in a more controlled way inside a single loop to make sure you are only deleting what you have copied from pending into items.
$sql = "SELECT photo,title,description, name FROM pending";
$select_pending = $conn->prepare($sql);
$select_pending->execute();
$sql = "INSERT INTO items (photo,title,description, name)
VALUES (:photo,:title,:description, :name)";
$insert_items = $conn->prepare($sql);
$sql = "DELETE FROM pending WHERE id = :id";
$delete_pending = $conn->prepare($sql);
// only if you are using INNODB databases.
//$conn->beginTransaction();
while( $row = $select_pending->fetch_object() ) {
$insert_items->bindParam(':photo', $row->photo, PDO::PARAM_STR);
$insert_items->bindParam(':title', $row->title, PDO::PARAM_STR);
$insert_items->bindParam(':description', $row->description, PDO::PARAM_STR);
$insert_items->bindParam(':name', $row->name, PDO::PARAM_STR);
$insert_items->execute();
$delete_pending->bind_param(':id', $row->id, PDO::PARAM_INT);
$delete_pending->execute();
}
// only if you are using INNODB databases.
//$conn->commit();
$sql = "INSERT INTO items (photo,title,description, name)
SELECT photo,title,description, name FROM pending";
remove the () in the SELECT statement.
Please help with my database design or sql optimization:
I have two tables stock and stock_tally. The stock table contain the list of items while the stock_tally contains all transactions on the items.
My Queries?
Add new product
add product to stock table
add same to stock_tally
Issue product
remove stock from stock table
enter code here retrieve stock_level from stock table
add the stock_level and the quantity to issue into stock_tally
Issue code
try{
$this->db->beginTransaction();
$q1 = $this->db->prepare("UPDATE stock SET stock_level =? where id=?");
$res1 = $q1->execute(array($qty, $id ));
$q2 = $this->db->prepare("SELECT * FROM stock WHERE id= ?");
$q2->bindValue(1, $id);
$q2->execute();
$res2 = $q2->fetch();
$q3 = $this->db->prepare("INSERT INTO stock_TALLY (id_fk, stocl_level, issues, update) VALUES (?,?,?,?)" );
$res3 = $q1->execute(array($id,$res2,0, $qty));
if($res2 && $res3){
$this->db->commit();
return true;
}
}catch(PDOException $e){
$this->db->rollBack(); //echo $e->getMessage();
return false;
}
The first thing you would do when designing a database is to write down clearly what it should be able to do, now, and possibly in the near future. You haven't done that, or at least you haven't told us. How do you expect us to tell you whether your database design and sql are optimal?