0

I have following PHP loop + SQL Update query.

for ($i=0;$i<count($_POST['id']);$i++) {

    if(!isset($_POST['live'][$i])){
        $_POST['live'][$i] = "0";
    } else { $_POST['live'][$i] = "1"; }

    $id = ($_POST['id'][$i]);
    $live = ($_POST['live'][$i]);
    $usr2 = $_SESSION['usr'];
    $updated = date("F j, Y, g:i a",time()+60*60);

    $sql = "UPDATE news SET live = '$live', usr2 = '$usr2', updated = '$updated' WHERE id = $id";
    $result = mysql_query($sql);
    //echo $sql."<br />";


}
if($result) {
    header("location: notes.php");
    exit();

}else {
    die("Query failed");
}

How does it work:

  • I'm submitting big form will ALL OF THE table rows.
  • receiving this in different file as an array
  • if $_POST['live'] is 'not set' - set it to '0', if 'set' set it to 1
  • update array data within for loop

How to UPDATE only the rows which have been actually been changed?

Those which value from $_POST['live'] is actually different from this saved in DB, as the condition would be change of our $live row.

2
  • @cularis - as it is localhost only, will worry about that later + this is behind secure login + validation on top of every file. Commented Aug 18, 2011 at 10:27
  • you could track which rows gets changed in the table and add a flag to mark that change Commented Aug 18, 2011 at 10:31

3 Answers 3

3

I guess you're concerned about the updated field and that this value only changes when something has been altered. (If that's not the case forget about this answer.)
You can define an ON UPDATE CURRENT_TIMESTAMP clause for a timestamp field. Each time a record is updated without explicitly setting a value for this field mysql uses the current time as its new value...
...but only if the record is altered; if you "update" the fields with the same value as are already in that record nothing happens.

demo script:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

setup($pdo);
$stmt = $pdo->prepare('UPDATE soNews SET somevalue=:v WHERE id=:id');
show('original', $pdo);

$stmt->execute( array(':id'=>1, ':v'=>'z') );
show('update with new=old & id=1', $pdo);

$stmt->execute( array(':id'=>2, ':v'=>'y') ); // new value=old value
show('update with new!=old & id=2', $pdo);



function setup($pdo) {
    $pdo->exec('
        CREATE TEMPORARY TABLE soNews ( 
            id int auto_increment,
            somevalue varchar(32),
            updated TIMESTAMP DEFAULT 0 ON UPDATE CURRENT_TIMESTAMP,
            primary key(id)
        )
    ');

    $pdo->exec("INSERT INTO soNews (id,somevalue,updated) VALUES (1,'x', Now()-interval 47 hour),(2,'y', Now()-interval 47 hour)");
}

function show($label, $pdo) {
    echo "------ $label --------\n";
    foreach( $pdo->query('SELECT * FROM soNews', PDO::FETCH_ASSOC) as $row ) {
        echo join(', ', $row), "\n";
    }
}

prints

------ original --------
1, x, 2011-08-16 14:09:53
2, y, 2011-08-16 14:09:53
------ update with new=old & id=1 --------
1, z, 2011-08-18 13:09:53
2, y, 2011-08-16 14:09:53
------ update with new!=old & id=2 --------
1, z, 2011-08-18 13:09:53
2, y, 2011-08-16 14:09:53

As you can see as a result of the first update query the timestamp field has been updated while the second query setting new=old didn't affect the updated field.

Sign up to request clarification or add additional context in comments.

2 Comments

is PDO really necessary for localhost / login secure page (including session check) usage?
It's just the utility of choice for me ;-) But no, the ON UPDATE CURRENT_TIMESTAMP has nothing to do with PDO and will work with the old php-mysql module as well. But you really should add the missing calls to mysql_real_escape_string() to your script. It doesn't have to be a malicious attacker; it can also be a (frustated) user who simply wants to put his/her name O'Neil into the database who breaks your system. But still, (server-side) prepared statements and named parameters are better.
3

Bobby tables will destroy your database. All your bits are belong to him (strictly speaking, this is an exaggeration, but you need to wrap all of your db inputs with mysql_real_escape_string or better yet, move to PDO's or MySQLi).

Long and the short? No, there is no reliable way to determine whether user input is the same as what is in the database without actually querying the database first or somehow storing the original output from the DB locally ($_SESSION or whatnot).

There are legitimate use cases for that, but it looks like you're better off just calling the updates. You can prevent them slightly by adding AND LIVE != '$live' AND UR2 != '$ur2', but you'll still need to run that many queries.


BTW -- I generally advise people not to use traditional for loops in PHP pretty much ever. PHP's foreach is better in almost every way. Instead of for ($i=0;$i<count();$i++), use foreach( $_POST['id'] as $i => $id ). You'll already have $id declared.

6 Comments

this is not true - he's using mySql, and he can't add ; DROP TABLES students --
@genesis He's open to Bobby Table's methods. Someone could put, 0' or 1 = 1 for the ID.
@cwallenpoole - Is PDO really necessary for localhost / login secure page (including session check) usage? Can you supply foreach example and explain the advantage of this method?
@NewUser The foreach syntax first, does not call a function repeatedly - count($_POST['id']) counts every time. It arguably uses less memory. It is a linguistic construct -- while a for(;;) loop is not foreign to PHP, foreach is something built in to the language. Finally, if you somehow mis-order your ID's in an array (say array(1=>'zero',0=>'one',2=>'two')) for(;;) iteration goes through in numeric order, while foreach follows "first in first out." As an example, switch for($i=0;$i<count($_POST['id']);$i++) with foreach($_POST['id'] as $i=>$id). The behavior will be the same.
+1 for bringing Little Bobby Tables to the party. He's such a helpful young man.
|
0

Actually, I think that a good way of doing is to:

1) Perform a query to get the old record from the db, then store the row contents in an associative array, with column names as keys.

2) Create a new array by checking the content of each "column" to be updated. If the content received is different from the value stored on the db, update the record data, else ignore and go ahead. Finally send back the updated data to the db with an UPDATE

function updateRecord(array $input_data) {

   // Get the data associated to the record id we want to update.
   $record_data = $yourDBWrapperClass
                     ->where("id",$_GET['record_id'])
                     ->get(TABLE_NAME);  

   // Process column by column and append data to the final array.

   $final_data = [];
   $ignored_columns = ['id', 'last_update'];

   foreach ($record_data as $col_name => $value) {

       // Update the record data, only when input data shows a difference
       if(array_key_exists($col_name, $input_data) 
           && 
          $record_data[$col_name] != $input_data[$col_name]) 
       {
          $final_data[$col_name] = $inputData[$col_name];
       }
       else if (array_key_exist($ignored_columns, $col_name) == FALSE && $record_data[$col_name] == $input_data[$col_name] 
       {
          $final_data[$col_name] == $value;
       }

   }


  // Finally, perform the db update.

  $update_result = $yourDBWrapperClass
                       ->where("id", $_GET['record_id'])
                       ->update(TABLE_NAME, $final_data);


  return $update_result ? "Record update success" : "Record update failed";

}

note: You don't need to send back the id, or last_update columns: their value is calculated automatically by the server. Sending a wrong value, will cause an error, or provide a wrong information. Think about the last_update column: it's better to leave to MySQL, which will call use the column default to get the value: NOW(); or CURDATE();

expected aspect of variables/arrays

$_GET['record_id'] = 123;

$record_data = [
   "id" => 123,
   "name" => "John",
   "surname" => "Dahlback",
   "age" => 31,
   "last_update" => "2019-01-01 10:00:00"
];

$input_data = [
   "age" => 32
];

$final_data = [
  // "id" => 123,
  "name" => "John",
  "surname" => "Dahlback",
  "age" => 32,
  // "last_update" => CURDATE();
];

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.