How to select one row after update in one query with PDO and PHP?
like this:
function giftUpdate($username,$id,$date,$conn){
$update = $conn->prepare("UPDATE gifts SET used = 1 , user = :user , date = :date WHERE id = :id");
$update->bindParam("id" , $id );
$update->bindParam("date" , $date );
$update->bindParam("user" , $username );
$update->execute();
return $update ? true : false;
}
function giftGet($username,$currnetTime,$conn) {
$statment = $conn->prepare("SELECT * FROM gifts WHERE used = 0 ORDER BY `id` ASC");
$statment->execute();
$gift = $statment->fetch(PDO::FETCH_OBJ);
if($gift){
$voucher = $gift->code;
$voucherid = $gift->id;
$vouchertype = $gift->type;
$voucherkey = $gift->keys;
if($voucher){
$giftUpdate = giftUpdate($username,$voucherid,$currnetTime,$conn);
if($giftUpdate){
useUpdate($username , $conn);
}
}
}
usleep(50000);
return $gift ? $gift : false;
}
After update I need select the same row to send it to user.
(i`am one table for vouchers list with 1000row and column 'used' with 0 by default , and with user request i need in one query update this row to be used 1 and select this row.)
my problem in this functions is Some users will receive a duplicate vouchers And a code is assigned to several users.
guys ... for this case Is it possible to use the transaction method? Or do you suggest using a transaction at all?
Edited:
I eventually changed the code below, but I'm still in line :
SELECT id FROM gifts WHERE used = 0 ORDER BY id ASC limit 1 FOR UPDATE)");
But when I put an id from the database instead of this code, it works!
function giftGet($username, $currnetTime, $conn)
{
$uniqid = uniqid('vouchers_');
$update = $conn->prepare("UPDATE gifts SET used = 1, uniqueid =:uniqueid , user = :user , date = :date WHERE id = (
SELECT id FROM gifts WHERE used = 0 ORDER BY `id` ASC limit 1 FOR UPDATE)");
$update->bindParam("date", $currnetTime);
$update->bindParam("uniqueid", $uniqid);
$update->bindParam("user", $username);
$update->execute();
$updaterowCount = $update->rowCount();
if ($updaterowCount)
{
$statment = $conn->prepare("SELECT * FROM gifts WHERE uniqueid = :uniqueid ORDER BY `id` ASC");
$statment->bindParam("uniqueid", $uniqid);
$statment->execute();
$gift = $statment->fetch(PDO::FETCH_OBJ);
$voucherid = $gift->id;
if ($gift)
{
useUpdate($username, $voucherid, $conn);
}
}
return $gift ? $gift : false;
}
I would alter the table gifts and add a column uniqueid(varchar).
then use
$uniqid = uniqid('vouchers_');
and rewrite your update
"UPDATE gifts SET used = 1, uniqueid =:UNIQUEID , user = :user , date = :date WHERE id = (
SELECT id FROM gifts WHERE used = 0 ORDER BY `id` ASC limit 1 FOR UPDATE)";
now you can identify the updated record by uniqueid.
In Your Code there is a time-gap between the select and the update. 2 or more users can start at the same time the select, recieve the same voucher before it is updated with used = 1
Related
I have a "matrix" table with the following columns filled in.
matrix_id, user_id, position_1, position_2, position_3
1 1 1982 2251 5841
2 2 6204 0 0
3 3 0 0 0
4 4 0 0 0
I basically want to do the following.
Find a row with the lowest user_id and that has an empty position.
In the example above, that would be user_id 2 and position_2.
I update the row with a query.
I then move on to the next next empty position. Since user_id 2 still has an empty position_3, I update the row again with a query.
Since that row is complete, I move on to the next highest user_Id that has empty positions. In this case, it's user_id 3 and then user_id 4 the one after that.
I know I can do all of the above if I know what the user_id is. But assume in this case, I have no clue what the user_ids are. How would the queries look then?
Here is what I have so far.
$find_user = $db->prepare("SELECT * FROM matrix WHERE user_id > :user_id");
$find_user->bindValue(':user_id', 0);
$find_user->execute();
$result_user = $find_user->fetchAll(PDO::FETCH_ASSOC);
if(count($result_user) > 0) {
foreach($result_user as $row) {
$matrix_id = $row['matrix_id'];
$user_id = $row['user_id'];
$position_1 = $row['position_1'];
$position_2 = $row['position_2'];
$position_3 = $row['position_3'];
}
} else {
$errors[] = 'User Id not found in Matrix.';
}
$update_user = $db->prepare("UPDATE matrix SET position_2 = :position_2 WHERE user_id = :user_id");
$update_user->bindValue(':position_2', 1564;
$update_user->bindParam(':user_id', $user_id);
if($update_user->execute()) {}
This should go through all your users from smallest user_id to largest.
For each user it will check the relevant columns in order and apply a new value to the empty ones.
$new_val = 1999;
$result = $db->query("SELECT * FROM matrix ORDER BY user_id");
$users = $result->fetchAll(PDO::FETCH_ASSOC);
if(count($users) > 0) {
// prepare all the possible queries
// make use of prepare once execute many times
$stmt1 = $db->prepare("UPDATE `matrix` SET `position_1` = :pos WHERE `user_id` = :id");
$stmt2 = $db->prepare("UPDATE `matrix` SET `position_2` = :pos WHERE `user_id` = :id");
$stmt3 = $db->prepare("UPDATE `matrix` SET `position_3` = :pos WHERE `user_id` = :id");
foreach($users as $user) {
if ( $user['$position_1'] == 0 ) {
$stmt1->execute( array(':pos'=>++$new_val,':id'=>$user['user_id']) );
}
if ( $user['$position_2'] == 0 ) {
$stmt1->execute( array(':pos'=>++$new_val,':id'=>$user['user_id']) );
}
if ( $user['$position_3'] == 0 ) {
$stmt1->execute( array(':pos'=>++$new_val,':id'=>$user['user_id']) );
}
}
} else {
$errors[] = 'User Id not found in Matrix.';
}
You could reduce the rows to process by changing the query a bit to only find users with columns to fix
$result = $db->query("SELECT *
FROM matrix
WHERE position_1 = 0
OR position_2 = 0
OR position_3 = 0
ORDER BY user_id");
important thing here, is that you are working with the row, not columns
so check all the prerequisites and update the row.
$find_user = $db->prepare("SELECT * FROM matrix order by user_id asc");
$find_user->execute();
$result_user = $find_user->fetchAll(PDO::FETCH_ASSOC);
foreach($result_user as $row) {
$matrix_id = $row['matrix_id'];
$user_id = $row['user_id'];
$position_1 = $row['position_1'];
$position_2 = $row['position_2'];
$position_3 = $row['position_3'];
}
$str = ''
if (!$position_2){
$str = "position_2 = :position_2"
} else if (!$position_2 && !$position_3){
$str = "position_2 = :position_2 and position_3 = :position_3"
}
$update_user = $db->prepare("UPDATE matrix SET " . $str . " WHERE user_id = :user_id");
$update_user->bindValue(':position_2', 1564);
$update_user->bindValue(':position_3', 1564);
$update_user->bindParam(':user_id', $user_id);
if($update_user->execute()) {}
Also, get all the rows in the matrix table ordered by used_id and process every row, depending on your condition.
I am writing a code for MySQL to fetch the 1st row with status is "inactive" and make them "active", but whenever I tried to update the column and make it "active" my query updates multiple rows rather than the single row.
date_default_timezone_set('Asia/Kolkata');
$d = time ();
$date = date("Y-m-d", $d);
$customer_id="1470831854";
$member_details="SELECT * FROM login_update WHERE customer_id ='$customer_id' AND status='inactive' ORDER BY id ASC LIMIT 1 ";
$member = mysql_query($member_details);
while($list = mysql_fetch_array($member)){
$status = $list['status'];
$id = (int)$list['id'];
}
$date_update = "UPDATE login_update SET status='active' WHERE id = '$id'";
$enter_date = mysql_query($date_update);
I think your code should be change as follow
while($list = mysql_fetch_array($member)){
$status = $list['status'];
$customerId = (int)$list['customer_id'];
}
$date_update = "UPDATE login_update SET status='active' WHERE id = '$customerId'";
$enter_date = mysql_query($date_update);
becasue if you get $list['id'] it is always return only 1 unique value from the database then update only one record. I assumed id is your primary key.
I made this php script and i am tryin to make it to return next and previus row, but there is one problem when i input my id the script return different thing for example :
This is my DB
ID String
1 Test 1
2 Test 2
3 Test 3
4 Test 4
So if i put ./index.php?id=1 this returns the result of id=2 and id=2 => id=3 and so on...
My question is how to fix it to return accurate result not +1. I tried with <= or => operators the result is correct, but then my links doesnt work.
Here is the script
<?php
if(isset($_GET['id']))
{
$id = (int)$_GET['id'];
}else
{
$id = 0;
}
$stmt1 = $db->prepare("SELECT * FROM records WHERE id > ? ORDER BY id ASC LIMIT 1");
$stmt1->bindValue(1,$id);
$stmt1->execute();
$row = $stmt1->fetch();
$stmt2 = $db->prepare("SELECT * FROM records WHERE id < ? ORDER BY id DESC LIMIT 1");
$stmt2->bindValue(1,$id);
$stmt2->execute();
$row = $stmt2->fetch();
echo $row['id'];
echo "<br/>";
echo $row['string'];
?>
I am not sure if the problem as silly as that, but I have no other explanation.
To have your page you need to make 3 selects:
to get current page data
to get prev id
to get next one
But I can see only 2 selects
So, you have to select data for the very page to show
if(isset($_GET['id']))
{
$sql = "SELECT * FROM records WHERE id = ?";
$stm = $db->prepare($sql);
$stm->execute(array($_GET['id']));
} else {
$sql = "SELECT * FROM records ORDER BY id ASC LIMIT 1";
$stm = $db->query($sql);
}
$row = $stm->fetch();
and now you can go for getting prev and next ids
$sql = "SELECT id FROM records WHERE id < ? LIMIT 1";
$stm = $db->prepare($sql);
$stm->execute(array($row['id']));
$prev = $stm->fetchColumn();
$sql = "SELECT id FROM records WHERE id > ? LIMIT 1";
$stm = $db->prepare($sql);
$stm->execute(array($row['id']));
$next = $stm->fetchColumn();
i am tryin to make it to return next and previus row
There is no such thing as "previous" or "next" row in a table. Without explicit ordering, tables must be considered as unordered set of rows. And you shouldn't rely on auto_increment field to be sequentially numbered. For example:
because there was interleaved insert on the table,
because the server is allowed to reuse auto_increment after row deletion.
You probably have to modify your table structure to add a sequence number:
CREATE TABLE tbl (id in primary key not null auto_increment,
sequence_number int unique,
value char(40));
While inserting your data you might rely on something like that:
INSERT INTO tbl (sequence_number, value)
VALUES (SELECT COUNT(*) FROM tbl, ?)
And the query for the "next" and "prev":
SELECT * FROM tbl WHERE sequence_number = ?-1 OR sequence_number = ?+1
ORDER BY sequence_number;
Assume that I have 3 tables namely sales_order , sales_order_items , invoice and set ON UPDATE = NO ACTION ON DELETE = NO ACTION.
Now I need to have the ability do delete the sales order. But if there is a relation exists in any other table it means that that sales order is used somewhere else and i need to prevent deletion.
Ex: I have sales_order_id = 34 , I want to check its exists in any other tables.
Previously to achieve the same thing I used transactions, something like below
$db = new database();
//start transaction
$db->start_trans();
//try to delete the sales order with ID = 34
$db->exec( 'DELETE FROM SALES_ORDER WHERE ID = 34' );
//check transaction success or failure
if( $db->trans_status() == true ){
//THERE IS NO RELATION EXISTS
//ROLLBACK
$db->rollback_trans();
#Soft Delete the record
$db->exec( 'UPDATE sales_order SET is_deleted = 1 WHERE id = 34' );
}else{
//RELATION EXISTS FOR ID = 34 IN SOME OTHER TABLES
}
the above code works, but the problem is with sales_order_items. Because it is a child table of sales_order and if it have contents then the transaction will fail and will try to execute the else part.
But in fact sales_order_items is a property of sales_order and I need to DELETE that particular sales_order (don't care about items).
I am expecting something like this
$relations = $db->get_relation( 'sales_order.id', '34' );
Expected Output
array( 'sales_order_items','invoice','another_table' .... );
Note: The above is an example only, I have number of tables and it's not possible to go through each table and check ID exists.
select * from information_schema.table_constraints
where constraint_schema = DATABASE();
select * from information_schema.key_column_usage
where constraint_schema = DATABASE();
SQLFIDDLE
Thanks Revoua for providing a good answer. Here is little modified version to suite my needs. Please correct me if it have some better way.
function check_relation( $table_name , $table_column ,$table_value ){
//Get relation tables
$qry = "SELECT table_name,
column_name,
referenced_table_name,
referenced_column_name
FROM information_schema.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_SCHEMA = ?
AND REFERENCED_TABLE_NAME is not null
AND referenced_table_name = ?
AND referenced_column_name = ?";
$db = $this->crm->db;
//database name
$database = $db->dbprefix.$db->database;
//predefined array to store table names
$related_tables = array();
//execute the query
$qry = $db->query( $qry , array( $database , $table_name ,$table_column ) );
unset($database);
//Make sure that query is success
if( $qry != FALSE && $qry->row_count() > 0 ){
//Get as array
$result = $qry->to_array();
unset($qry);
foreach( $result as $result_item ){
//Get count based on table_value
$qry = "SELECT COUNT(*)as total FROM ".$result_item['table_name']." WHERE ".$result_item['column_name']." = ? and is_del = 0";
$qry = $db->query($qry,array($table_value));
if( $qry!= FALSE && $qry->row_count() > 0 ) {
$qry = $qry->to_array(1);
//There are some entries ..
if( intval($qry['total']) > 0 ){
$related_tables[] = $result_item['table_name'];
}
}
}
return $related_tables;
}else{
return $related_tables;
}
}
Usage:
$relation = $db->check_relation("SALES_ORDER","ID",34);
var_dump( $relation ) ==>
array( 'SALES_ORDER_ITEM','INVOICE' ... );
Now in business logic
if( empty( $relation ) ){
// No relation exists
}elseif( in_array( 'invoice', $relation ) ){
$message->set_message("Cannot delete Sales Order, Invoice exists.");
}else ...
I have this query:
SELECT id, result, ip_address, added_date
FROM results
WHERE course_id = (
SELECT id
FROM courses
WHERE course = 'informatica'
AND macro_course_id = (
SELECT id
FROM macro_courses
WHERE macro_course = 'scienze-matematiche-fisiche-e-naturali'
AND organization_id = (
SELECT id
FROM organizations
WHERE organization = 'universita-degli-studi-di-torino'
AND city_id = (
SELECT id
FROM cities
WHERE city = 'torino'
AND region_id = (
SELECT id
FROM regions
WHERE region = 'piemonte' ))))) ORDER BY id DESC
And i'm using this code to do it with a preparedstatement
public function getResults($region, $city, $organization, $macro_course, $course) { //works
//added_date=datetime : YYYY-MM-DD HH:mm:ss
echo "SELECT id, result, ip_address, added_date
FROM results
WHERE course_id = (
SELECT id
FROM courses
WHERE course = '$course'
AND macro_course_id = (
SELECT id
FROM macro_courses
WHERE macro_course = '$macro_course'
AND organization_id = (
SELECT id
FROM organizations
WHERE organization = '$organization'
AND city_id = (
SELECT id
FROM cities
WHERE city = '$city'
AND region_id = (
SELECT id
FROM regions
WHERE region = '$region' ))))) ORDER BY id DESC"; //just for me to know what query is being executed
if ($stmt = $this->mysqli->prepare(("
SELECT id, result, ip_address, added_date
FROM results
WHERE course_id = (
SELECT id
FROM courses
WHERE course = ?
AND macro_course_id = (
SELECT id
FROM macro_courses
WHERE macro_course = ?
AND organization_id = (
SELECT id
FROM organizations
WHERE organization = ?
AND city_id = (
SELECT id
FROM cities
WHERE city = ?
AND region_id = (
SELECT id
FROM regions
WHERE region = ? ))))) ORDER BY id DESC
"))) {
$return = array();
$stmt->bind_param('sssss', $course, $macro_course, $organization, $city, $region);
$stmt->execute();
if ($stmt->fetch()) {
$i = 0;
while ($row = $stmt->fetch()) {
print_r($row);//this is never reached
continue;
$s = new Result($row['result'], $row['added_date'], $row['id']);
$return[$i] = $s;
$i+=1;
}
}
}
return $return;
}
The problem is that this function returns 0 rows and 0 errors (checked with $this->mysqli->error), it seems that $row = $stmt->fetch() is always false.
However, if i copy and execute on PHPMyAdmin the output i get at the function top, i see
Showing lines 0 - 0 ( 1 total, Query time 0.0003 sec)
So the query returns a line but it is not catched by php. What am i missing? How can i fix this?
Because you used $stmt-fetch() two times here
if ($stmt->fetch()) {
$i = 0;
while ($row = $stmt->fetch()) {
Remove the if ($stmt->fetch()) condition, it will work as expected.
EDIT
from docs
Note that all columns must be bound by the application before calling $stmt->fetch().
You have to bind the result before calling $stmt->fetch() like this
/* bind result variables */
$stmt->bind_result($name, $code);