Running multiple queries in PHP insert erroneous values - php

I have run into a problem I have never seen before. The code is too much and large and too many classes, so pardon me for not going into much detail.
The interesting bit is this:
$copy = clone $this;
$copy->workerid = $new_workerid;
if ( $copy->mysql_create_row( $this->pending_table ) )
return $copy;
This runs as expected. When I go into the database, I see a new entry in table Pending.
When however I run multiple queries in succession:
$copy = clone $this;
$copy->workerid = $new_workerid;
if ( $copy->mysql_create_row( $this->pending_table ) )
if ( $this->mysql_delete_row( $this->pending_table ) )
if ( $this->mysql_create_row( $this->cancelled_table ) )
return $copy;
Then for some unexplainable reason, the wrong id column is inserted at one of the tables.
In specific method $copy->mysql_create_row, among other things, does this:
if ( $con = db_connect() ) {
$res = mysqli_query( $con, "INSERT INTO $table (`workerid`,`time`,`location`,`tasks`) VALUES ('$this->workerid','$this->datetime','$this->location','$this->tasks');" )
or error_log( "mysql_create_row Error: " . mysqli_error( $con ) );
if ( $res ) {
$this->contactid = mysqli_insert_id( $con );
foreach ( $this->attendees as $att ) {
$att->__setContactId( $this->contactid );
if ( !$att->mysql_create_row( $this->attendee_table ) ) {
error_log( " Contact->mysql_create_row Error: Did not create attendee MySQL row" );
return false;
}
Delegation through object method $att->mysql_create_row( $this->attendee_table ) works the same way, it is merely an INSERT query.
Now this is where the things make no sense:
Whereas the newly inserted $copy object inserts the correct id at $copy->mysql_create_row, the $att->mysql_create_row does not !!!
It inserts the old id.
Furthermore, when I echo out the query from $att->mysql_create_row, it echoes out the correct (new id)!!!
But when I go into the actual database, it has inserted the old id. This makes absolutely no sense to me.
I have tried to create custom __clone() methods, try to make sure that I have copies and not references (damn you php) but it still produces the same error.
The problem seems to be with the successive queries, as when I do not do the delete or the new insert, I do not have any problems.
The delete works fine, and the second insertion also works fine, without any errors whatsoever.
What could possibly force the MySQLi api to insert the wrong id, while the query echoes the correct one ?
I could only thing of some form of background/multithreaded corruption, but since I have no control over this sort of thing, I do not even know how to tackle the problem.
When echoing out the query from $att->mysql_create_row, I get:
before query id: 305
'INSERT INTO XXXXXXXX.attendees
(contactid,title,forenames,surname,relationship,gender,age,arrived,left,supervised,ics_number)
VALUES ('305','Mr',
'Alex','Werner','','M','29/08/1981','0','','','0');'
after query id: 305
But when I actually go into the database, the value inserted is the old id value (say 294).
The table column is not Auto-incrementing, is not unique or primary, it is simply a int(11).
What on earth could be causing this ?

Your problem could be that you are using mysql_last_insert_id to get the id of the last inserted row. As you are inserting multiple rows, it could be possible that you don't get the correct id.
Replace mysql_last_insert_id call with a select query that returns the id of your last insertion and look if your problem is solved.

Related

Doctrine and PDO not giving the same results

Okay, the code below I have created to show the issue.
And yes, I appreciate the fact that it should ALL be doctrine and by using PDO it makes the whole doctrine thing kinda pointless.
More importantly though, I am trying to understand why the two don't match up.
Basically, the first PDO query comes back with: 70 row count and 140 Max ID.
The second PDO query returns the same 70 row count and 140 Max ID.
The problem is, the doctrine insert between the two has made it 71 and 141.
Why is the second PDO query note reflecting this?
If I run the query in a separate PHP page or in Workbench, it returns 71/141. It just doesn't within the executing page.
$userID = $enUser->getId();
$stmt1 = $this->pdoConnection->getConnection()->prepare('
SELECT COUNT(UT.ID) AS UTC,
MAX(UT.ID) AS UTMID
FROM UserThing UT
WHERE UT.User_ID = :userID;
' ) or die ( implode( ':', $stmt1->errorInfo( ) ) );
$stmt1->bindParam( ':userID', $userID, \PDO::PARAM_INT, 11 );
$results = $this->pdoConnection->dbRecSet( $stmt1, false );
$stmt1->closeCursor();
unset($stmt1);
var_dump($results);
$newUserThing = new UserThing();
$newUserThing->setUserThingThings( $enThing );
$newUserThing->setUserThingUsers( $enUser );
$newUserThing->setAttainedDate( new \DateTime() );
$this->em->persist( $newUserThing );
$this->em->flush();
$userThingID = $newUserThing->getId();
echo $userThingID;
$stmt2 = $this->pdoConnection->getConnection()->prepare('
SELECT COUNT(UT.ID) AS UTC,
MAX(UT.ID) AS UTMID
FROM UserThing UT
WHERE UT.User_ID = :userID;
' ) or die ( implode( ':', $stmt2->errorInfo( ) ) );
$stmt2->bindParam( ':userID', $userID, \PDO::PARAM_INT, 11 );
$results = $this->pdoConnection->dbRecSet( $stmt2, false );
$stmt2->closeCursor();
unset($stmt2);
var_dump($results);
Any ideas why the second PDO query doesn't reflect the DB change, even though Doctrine is able to return the newly inserted ID?
The bit which didn't occur to me when making this post, was the fact that my PDO class used a different connection string to the Symfony one.
To be able to support multiple record sets to be returned from a single query, my class used different driver options to the default. So I just ported over the class into Symfony and used it and it worked well for a long time.
That said, doing an insert/update on one connection and reading straight after on another does not work well. That is the issue I found here.
The documentation for Symfony when it comes to PDO driver options is a bit lacking. For anyone facing it, all you need to know is that the PDO constants just need their Integer equivalents in the YML instead.
So you end up with things like this in your app/config/config.yml:
options:
20: true
3: 1
1002: "SET NAMES 'utf8' COLLATE 'utf8_unicode_ci'"
That allowed me to do what I needed to and do it correctly.

How to update database in moodle using cron

I am facing a problem. I have to update a field in database by using cron function in moodle. I am using update query in cron function to update value. But It doesn't work. I am using this function to update value:
function activitysetmodule_cron ()
{
global $CFG, $DB;
$DB->update_record("activitysetmodule",)
$sql="update {$CFG->prefix}activitysetmodule as asm set status = 1 where exists (select 1 from {$CFG->prefix}course_modules as cm where (module=asm.activityset OR module=asm.activityset2 ) AND completion=1 AND asm.course =cm.course ");
return true;
}
Please help to sought it out.
Take a look at the documentation https://docs.moodle.org/dev/Data_manipulation_API#Updating_Records
$DB->update_record takes 2 params, the name of the table to update the record in and an object containing the updated data.
e.g.
$obj = new stdClass();
$obj->id = $id_of_object_to_update;
$obj->status = 1;
$DB->update_record('tablename', $obj);
It looks like you should refactor your code to get a list of records to update, then call $DB->update_record on each in turn (or $DB->set_field, if there is only one field to update). Alternatively, you could use the $DB->execute($sql) function to directly run some SQL on the server, e.g.
$DB->execute("UPDATE {activitysetmodule} asm SET status = 1 WHERE EXISTS (SELECT 1 FROM {course_modules} cm WHERE (module=asm.activityset OR module=asm.activityset2 ) AND completion=1 AND asm.course = cm.course)");
Note the use of {tablename} rather than {$CFG->prefix}tablename and the removal of the 'AS' keyword, as that is not allowed on all DB engines.
Note also, if you haven't done so already, turning on debugging (http://docs.moodle.org/en/Debugging) will give you much more helpful error messages.

How can I group by column value in a yii query?

I have a data table with 7 columns and 400 records. One of them is budget. I want to group the 400 rows by budget so that I get an array like this:
[budget]=>array(
[0]=>array(
[column1]=>'1',
[column2]=>'sand'
),
[1]=>array(
[column1]=>'2',
[column2]=>'clay'
)
)
[budget2]=>array(
[0]=>array(
[column1]=>'3',
[column2]=>'silt'
),
[1]=>array(
[column1]=>'4',
[column2]=>'stone'
)
)
So far I have been playing around with Yii's CdbCommand and CdbDataReader and PHP's PDOStatement but nothing is working right. I tried the following code
public function actionBidCostByBudget(){
$command = Yii::app()->db
->createCommand()
->Select('*')
->From('bid_cost')
# ->Query()
;
echo '<pre>';
echo get_class($command);
$pdostatement=$command->getPdoStatement();
if($pdostatement) echo get_class($pdostatement);
# print_r($statement->fetchall(PDO::FETCH_COLUMN|PDO::FETCH_GROUP));
# print_r($command->readall());
# print_r($statement->fetch());
# $columnsArray = BidCost::attributeLabels();
//print_r($rowsArray);
//$this->layout='\\layout';
}
The attempts to print_r all print out with nothing. getPdoStatement equals nothing. I have been trying to use PDO::FETCH_COLUMN|PDO::FETCH_GROUP as per the Php.net website, but it does not work either because I get nothing.
One of Yii's strengths is it's ActiveRecord, so why not use it?
Make your budget to a separate table (so you can generate a model from it). Reference it from your "datatable".
CREATE TABLE budget (
id INTEGER PRIMARY KEY,
name TEXT
);
CREATE TABLE datatable(
column1 TEXT,
column2 TEXT,
...
budget_id INTEGER,
FOREIGN KEY(budget_id) REFERENCES budget(id)
);
Next generate models with Gii, and now you can use your newly made relations like this:
$budget = Budget::model()->findByAttributes( ["name"=>"budget2"] );
foreach( $budget->datatables as $dt ) {
echo $dt->column1;
echo $dt->column2;
}
(I know. Not the array you asked for. Sorry if I'm way off with this.)
Alright, the bottom line is that I was not able to find a way to do this right thru Yii, so I did it with a more hands-on approach.
The first thing I did was basically initiate a database connection thru Yii.
$command = Yii::app()->db //outputs CDbConnnection
The next thing I did was get a PDO class from the connection:
$pdoinstance = $command->getPdoInstance(); //outputs PDO class
From this point, it was help obtained from PHP.net and another question posted on this forum:
$pdostatement=$pdoinstance->prepare('SELECT BUDGET_CODE,
PAY_ITEM, ITEM, DESCRIPTION FROM bidcost');
$pdostatement->execute();
//default fetch mode could not be set
# $pdostatement->setfetchmode(PDO::FETCH_GROUP|PDO::FETCH_ASSOC);
//returns array
$testarray=$pdostatement->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_ASSOC);

PHP RedBean store bean if not exist one

I am a little confused. I actively use PHP RedBean as ORM within my direct mail service and I run into curious situation - I have a table with unique key constraint (i.e. subscriber_id, delivery_id) and two scripts that is writing data into this table.
There is source code that is inserting or updating table:
public static function addOpenPrecedent($nSubscriberId, $nDeliveryId)
{
$oOpenStatBean = \R::findOrDispense('open_stat', 'delivery_id = :did AND subscriber_id = :sid', array(':did' => $nDeliveryId, ':sid' => $nSubscriberId));
$oOpenStatBean = array_values($oOpenStatBean);
if (1 !== count($oOpenStatBean)) {
throw new ModelOpenStatException(
"Ошибка при обновлении статистики открытий: пара (delivery_id,
subscriber_id) не является уникальной: ($nDeliveryId, $nSubscriberId).");
}
$oOpenStatBean = $oOpenStatBean[0];
if (!empty($oOpenStatBean->last_add_dt)) {
$oOpenStatBean->precedent++;
} else {
$oOpenStatBean->delivery_id = $nDeliveryId;
$oOpenStatBean->subscriber_id = $nSubscriberId;
}
$oOpenStatBean->last_add_dt = time('Y-m-d H:i:s');
\R::store($oOpenStatBean);
}
It is called both from two scripts. And I have issues with corruption unique constraint on this table periodically, because race conditions occurs. I know about SQL "INSERT on duplicate key update" feature. But how can I obtain same result purely using my ORM?
Current, that I know if, Redbean will not issue an
INSERT ON DUPLICATE KEY UPDATE
as the discussion of this cited in the comments above indicates that Redbean's developer considers upsert to be a business logic thing that would pollute the ORM's interphase. This being said, it is most likely achievable if one were to extend Redbean with a custom Query Writer or plugin per the Documentation. I haven't tried this because the method below easily achieves this behavior without messing with the internals and plugins of the ORM, however, it does require that you use transactions and models and a couple of extra queries.
Basically, start your transaction with either R::transaction() or R::begin() before your call to R::store(). Then in your "FUSE"d model, use the "update" FUSE method to run a query that checks for duplication and retrieves the existing id while locking the necessary rows (i.e. SELECT FOR UPDATE). If no id is returned, you are good and just let your regular model validation (or lack thereof) continue as usual and return. If an id is found, simply set $this->bean->id to the returned value and Redbean will UPDATE rather than INSERT. So, with a model like this:
class Model_OpenStat extends RedBean_SimpleModel{
function update(){
$sql = 'SELECT * FROM `open_stat` WHERE `delivery_id`=? AND 'subscriber_id'=? LIMIT 1 FOR UPDATE';
$args = array( $this->bean->deliver_id, $this->bean->subscriber_id );
$dupRow = R::getRow( $sql, $args );
if( is_array( $dupRow ) && isset( $dupRow['id'] ) ){
foreach( $this->bean->getProperties() as $property => $value ){
#set your criteria here for which fields
#should be from the one in the database and which should come from this copy
#this version simply takes all unset values in the current and sets them
#from the one in the database
if( !isset( $value ) && isset( $dupRow[$property] ) )
$this->bean->$property = $dupRow[$property];
}
$this->bean->id = $dupId['id']; #set id to the duplicates id
}
return true;
}
}
You would then modify the R::store() call like so:
\R::begin();
\R::store($oOpenStatBean);
\R::commit();
or
\R::transaction( function() use ( $oOpenStatBean ){ R::store( $oOpenStatBean ); } );
The transaction will cause the "FOR UPDATE" clause to lock the found row or, in the event that no row was found, to lock the places in the index where your new row will go so that you don't have concurrency issues.
Now this will not solve one user's update of the record clobbering another, but that is a whole different topic.

Inserting array into database Yii

I would like to insert an array into a MYSQL database, preferably using Yii's active record.
For example, I have a an array:
User = array(
fname => "Joe"
lname => "Schmidt"
)
with a User table in my database with columns id, fname and lname. One of the options is creating an object and doing:
$user = new User;
$user->fname = User['fname'];
$user->lname = User['lname'];
$user->save();
However, this seems like so much code for such common functionality. Is there a way to insert an array into the database where array keys match corresponding columns without me writing my own function or doing some SQL query hack? Ideally it uses the already present Active record of Yii.
What you want to do is handled by the framework itself.
You can mass assign like:
$user->attributes=$_POST['User'];
Read more about Mass Assignment
I have never worked with Yii before, so I can't offer a solution using that, but you can serialize the array and store it in the single cell in your database, like so:
$user = array("fname" => "Joe", "lname" => "Schmidt");
$serialized = serialize($user);
//Store the $serialized variable in the database
When you are ready to access it:
//Get your data from the database
$unserialized = unserialize($usersFromDB);
$fname = $unserialized['fname']; //Joe
Hope that helps.
the function is pretty straightforward, try this:
function insert($table, $fields_values = array())
{
$q1 = join(",", array_keys($fields_values));
$q2 = "'".join("','", array_values($fields_values))."'";
$query = "INSERT INTO $table($q1) VALUES($q2)";
// do your DB insert here
}
The main trick is the array to query conversion using join and array_keys / array_values.
Depending the amount of data in array you can write you own function e.g
a) check this backUpdate , modify it to insert /remove render view option
b) Follow this thread
c) Possible traps when inserting multiple records
d) check this associated SOQ
If you know what you are doing its easy to do , you just need to take care of
validations
records exists in associated tables ( if there is FKey involved )
option d). will be a posssible answer if you have simple inserts ( with no associated tables)

Categories