I'm getting "Duplicate entry" on an insert query that worked fine with the old mysqli functions. Once I converted to PDO, the same insert statement breaks.
Here's the sample SQL data
CREATE TABLE IF NOT EXISTS `lookup_codes` (
`id` int(3) NOT NULL,
`code` varchar(10) NOT NULL,
PRIMARY KEY (`id`,`code`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `lookup_codes` (`id`, `code`) VALUES
(1, 'AR'),
(1, 'CN'),
(1, 'OA'),
(4, 'AR'),
(4, 'OA');
And here is the example php/pdo code. (This is not a code critique request - just trying to boil down a problem to a simple example, do not use this code in production, yada yada).
// posted data from user
$id = 2;
$arr_codes = array('AR','CN');
// make sure existing rows do not exist
try
{
$q = "SELECT COUNT(*) FROM lookup_codes WHERE id=:id";
if ($dbg) { echo "<p>SELECT COUNT(*) FROM lookup_codes WHERE id=$id<br>";}
$stmt = $dbx_pdo->prepare($q);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
list($count_rows) = $stmt->fetch(PDO::FETCH_NUM); $stmt->closeCursor();
if ($count_rows>0)
{
try
{
$q = "DELETE FROM lookup_codes WHERE id=:id";
if ($dbg) { echo "<p>DELETE FROM lookup_codes WHERE id=$id<br>";}
$stmt = $dbx_pdo->prepare($q);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
$stmt->closeCursor();
} catch(PDOException $err) {
echo $err->getMessage());
exit;
}
}
} catch(PDOException $err) {
echo $err->getMessage());
exit;
}
// set up prepared statement based on posted data
foreach ($arr_codes as $code)
{
$arr_ip[] = array(
'id' => $id,
'code' => $code,
);
}
$txt_prepared = join(',', array_fill(0, count($arr_ip), '( ?, ?)'));
// try inserting
try
{
$q = "INSERT INTO lookup_codes (id, code) VALUES $txt_prepared";
$stmt = $dbx_pdo->prepare($q);
$cb = 1;
foreach ($arr_ip as $rip)
{
$id = $rip['id'];
$code = $rip['code'];
$stmt->bindParam($cb++, $id, PDO::PARAM_INT);
$stmt->bindParam($cb++, $code, PDO::PARAM_STR);
if ($dbg) { echo "<b>Binding</b> ($id, $code)<br>";}
}
$stmt->execute();
$stmt->closeCursor();
} catch(PDOException $err) {
echo $err->getMessage());
exit;
}
That's all there is on my test script. No header or meta redirects.. just a single run of code here.
The example above results in:
Error: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate
entry '2-CN' for key 'PRIMARY'
Note that it reports back duplicate on the second submitted code.
The same INSERT works just fine when using mysqli_query(). There are no errors written to the error log.
What is going on here?
The problem is the way you are binding on your insert query. You need to change $stmt->bindParam to $stmt->bindValue. bindParam takes a variable by reference so on each iteration of your loop as you change the value of $id and $code you are changing the value that was bound. bindValue will take a copy of the value from the variable and bind that instead.
Related
Good morning,
I wrote the code below that it's running on PHP with PDO and SQLite under transaction.
Inexplicably it does not go into error but it does not even write the second table which results with all the fields of the records at null.
Could you tell me what I'm doing wrong since I can't find any errors in the Apache2 logs?
Thank you very much.
I create two tables in SQLite:
The first contains the questionary data;
The second contains the details information for every questionary
My SQL script to create DB:
CREATE TABLE "questionari" (
"pkid_questionario" INTEGER NOT NULL,
"dt_registrazione" TEXT NOT NULL,
"matricola" INTEGER NOT NULL,
"cognome" TEXT NOT NULL,
"nome" TEXT NOT NULL,
"email" TEXT NOT NULL,
"pk_localizzazione" INTEGER NOT NULL,
"desc_localizzazione" TEXT NOT NULL,
PRIMARY KEY("pkid_questionario" AUTOINCREMENT)
);
CREATE TABLE "t1" (
"idQuestionario" INTEGER,
"pkidBene" INTEGER,
"NumeroEtichetta" INTEGER,
"DescBene" TEXT,
"EsistenzaBene" TEXT,
"EsistenzaEtichetta" TEXT DEFAULT null,
"txtNote" TEXT DEFAULT null,
FOREIGN KEY("idQuestionario") REFERENCES "questionari"("pkid_questionario")
ON UPDATE CASCADE
ON DELETE CASCADE,
PRIMARY KEY("pkidBene","idQuestionario")
);
My PHP code is this:
define("T1_PkID","t1_PkID_r");
define("T1_Etichetta", "t1_Etichetta_r");
define("T1_Desc", "t1_Desc_r");
define("T1_Bene_YN", "t1_Bene_YN_r");
define("T1_Etichetta_YN", "t1_Etichetta_YN_r");
define("T1_Note", "t1_Note_r");
//[Omissis...]
try{
$db = new SQLiteDB();
//$dbh = new PDO("sqlite:./DB/DB_Inventario.db");
$dbh = $db->connect();
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh->beginTransaction();
$sql = "INSERT INTO questionari ";
$sql .= "(dt_registrazione, matricola, cognome, nome, email, pk_localizzazione, desc_localizzazione) VALUES ";
$sql .= "(:dt_registrazione, :matricola, :cognome, :nome, :email, :pk_localizzazione, :desc_localizzazione)";
$stmt = $dbh->prepare($sql);
$dt_registrazione = date('Y/m/d H:i:s');
$stmt->bindParam(":dt_registrazione", $dt_registrazione);
$stmt->bindParam(":matricola", $matricola);
$stmt->bindParam(":cognome", $cognome);
$stmt->bindParam(":nome", $nome);
$stmt->bindParam(":email", $email);
$stmt->bindParam(":pk_localizzazione", $pkLocalizzazione);
$stmt->bindParam(":desc_localizzazione", $DescLocalizzazione);
$stmt->execute();
$lastID = $dbh->lastInsertId();
// t1_NumRows is the rows number to write for t1
for($i=0;$i<$t1_NumRows;$i++){
$sql = "INSERT INTO t1 ";
$sql .= "(idQuestionario, pkidBene, NumeroEtichetta, DescBene, EsistenzaBene, EsistenzaEtichetta, txtNote) VALUES ";
$sql .= "(:p_idQuestionario, :p_pkidBene, :p_NumeroEtichetta, :p_DescBene, :p_EsistenzaBene, :p_EsistenzaEtichetta, :p_txtNote)";
$stmt->bindParam(":p_idQuestionario", $lastID, PDO::PARAM_INT);
$stmt->bindParam(":p_pkidBene", $_POST[T1_PkID.$i], PDO::PARAM_INT);
$stmt->bindParam(":p_NumeroEtichetta", $_POST[T1_Etichetta.$i], PDO::PARAM_INT);
$stmt->bindParam(":p_DescBene", $_POST[T1_Desc.$i], PDO::PARAM_STR, 500);
$stmt->bindParam(":p_EsistenzaBene", $_POST[T1_Bene_YN.$i], PDO::PARAM_STR, 2);
if($_POST[T1_Bene_YN . $i]=="NO")
$stmt->bindParam(":p_EsistenzaEtichetta", null, PDO::PARAM_STR, 2);
else
$stmt->bindParam(":p_EsistenzaEtichetta", $_POST[T1_Etichetta_YN.$i], PDO::PARAM_STR, 2);
if(strlen(trim($_POST[T1_Note.$i]))==0)
$stmt->bindParam(":p_txtNote", null, PDO::PARAM_STR, 500);
else
$stmt->bindParam(":p_txtNote", $_POST[T1_Note.$i], PDO::PARAM_STR, 500);
$stmt = $dbh->prepare($sql);
$stmt->execute();
}
//[Omissis...]
$dbh->commit();
// In case of exception make rollback!
This is the result on "t1" after record first questionary to have two details:
Ok, I did some debugging in my code and I found that the prepare statement must be inserted immediately after the definition of the parametric queries.
At this point my code works: D: D
for($i=0;$i<$t1_NumRows;$i++){
$sql = "INSERT INTO t1 ";
$sql .= "(idQuestionario, pkidBene, NumeroEtichetta, DescBene, EsistenzaBene, EsistenzaEtichetta, txtNote) VALUES ";
$sql .= "(:p_idQuestionario, :p_pkidBene, :p_NumeroEtichetta, :p_DescBene, :p_EsistenzaBene, :p_EsistenzaEtichetta, :p_txtNote)";
$stmt = $dbh->prepare($sql);
$stmt->bindParam(":p_idQuestionario", $lastID, PDO::PARAM_INT);
$stmt->bindParam(":p_pkidBene", $_POST[T1_PkID.$i], PDO::PARAM_INT);
$stmt->bindParam(":p_NumeroEtichetta", $_POST[T1_Etichetta.$i], PDO::PARAM_INT);
$stmt->bindParam(":p_DescBene", $_POST[T1_Desc.$i], PDO::PARAM_STR, 500);
$stmt->bindParam(":p_EsistenzaBene", $_POST[T1_Bene_YN.$i], PDO::PARAM_STR, 2);
if($_POST[T1_Bene_YN . $i]=="NO")
$stmt->bindValue(":p_EsistenzaEtichetta", null, PDO::PARAM_STR, 2);
else
$stmt->bindParam(":p_EsistenzaEtichetta", $_POST[T1_Etichetta_YN.$i], PDO::PARAM_STR, 2);
if(strlen(trim($_POST[T1_Note.$i]))==0)
$stmt->bindValue(":p_txtNote", null, PDO::PARAM_STR, 500);
else
$stmt->bindParam(":p_txtNote", $_POST[T1_Note.$i], PDO::PARAM_STR, 500);
$stmt->execute();
}
How can I allow the user submitting a form, to update his entry on "re-submission"
for example
12345678910 (unique id) , submitted the form with selections,
12345678910 , re-submitted with new selections
what's the function responsible for "automatically" updating such kind of form entries.
I know that I can use a check if the entry exists, but how do I update it if it exists and insert it in a new row if it doesn't ...
function checkstudentid($studentid)
{
$con = connectvar();
mysql_select_db("database1", $con);
$result = mysql_query(
"SELECT * FROM table WHERE studentid='$studentid' LIMIT 1");
if(mysql_fetch_array($result) !== false)
....
// I want to add the entry here since it doesn't exist...with checkboxes
// else , I want to update if it already exists
}
Now I'm also not completely positive if the above code will work...but this is what I have for starters, if there is any other way or if the method I'm using is "wrong" , I would appreciate the heads up...or if what I'm trying to is even possible (the way I'm doing it)...
NOTES
I only have one php file which the form submits to.
I am not using a login/registration system
I do not want to display all the data in a table using HTML, just an
"automatic" update if the studentid already exists in the table
If I were using a deprecated method to interact with a database, I would probably just do this:
<?php
function checkstudentid($studentid) {
$con = connectvar();
mysql_select_db("database1", $con);
$result = mysql_query(
"SELECT * FROM table WHERE studentid='$studentid' LIMIT 1");
$query = '';
if (mysql_num_rows($result) > 0) {
$query = "UPDATE table SET column1='$value_one', column2='$value_two' WHERE studentid='$studentid'";
} else {
$query = "INSERT INTO table VALUES('$new_id', '$value_one', '$value_two')";
}
if (mysql_query($query)) {
return true;
} else {
return false;
}
}
?>
But then again, I would use PDO to interact with the DB.
Here is a simple PDO example (you just have to write the function to return the connection):
<?php
function checkstudentid($studentid) {
$update = false;
$dbh = formPDOConnection();
$query = "SELECT studentid FROM table WHERE studentid=:id";
$stmt = $dbh->prepare($query);
$stmt->bindValue(':id', $studentid, PDO::PARAM_STR);
if ($stmt->execute()) {
if ($stmt->rowCount()) {
$update = true;
}
} else {
return 'failure to execute query';
}
// if we just need to update
if ($update) {
$update = "UPDATE table SET value1=:v1,
value2=:v2 WHERE studentid=:id";
$stmt = $dbh->prepare($update);
$stmt->bindValue(':id', $studentid, PDO::PARAM_STR);
$stmt->bindValue(':v1', $value_one, PDO::PARAM_STR);
$stmt->bindValue(':v2', $value_two, PDO::PARAM_STR);
} else {
$insert = "INSERT INTO table VALUES(:id,:v1,v2)";
$stmt = $dbh->prepare($insert);
$stmt->bindValue(':id', $new_id, PDO::PARAM_STR);
$stmt->bindValue(':v1', $value_one, PDO::PARAM_STR);
$stmt->bindValue(':v2', $value_two, PDO::PARAM_STR);
}
return $stmt->execute();
}
?>
Save yourself a headache and stop using mysql_*
You can use INSERT... ON DUPLICATE KEY UPDATE... on your mysql code instead use the logic in your PHP.
Here's a sample:
INSERT INTO `category` (`id`, `name`) VALUES (12, 'color')
ON DUPLICATE KEY UPDATE `name` = 'color';
Reference: http://dev.mysql.com/doc/refman/5.6/en/insert-on-duplicate.html
I'm trying to generate a unique username that is not already in my database and then add it as a primary key into my InnoDB database table plus some other field entries.
I get the error code:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'ft6888' for key 'PRIMARY'
Checking the database table manually I can see that it does not already contain the value I'm passing and by echoing the username values I'm binding I can see each is only bound once. The field is not set to auto_increment as in some of these questions but it used as a foreign key in some other tables (but the values I'm binding don't exist in those either).
If I echo out the variables I'm binding just before I bind them I get two sets of correct data. When I insert this same data (copy and pasted) into the table using phpmyadmin it works fine no errors. I can only assume my code itself is somehow trying to insert twice?
$query = "INSERT INTO user_login (username, usertype, hashedpassword) VALUES";
$qPart = array_fill(0, count($excelData), "(?, ?, ?)");
$query .= implode(",",$qPart);
$sth = $dbh->prepare($query);
$i = 1;
$sql = "SELECT username FROM user_login WHERE username = :username";
$sthUser = $dbh->prepare($sql);
Foreach($excelData As $Row){
Do{
//Create unique userID
$finitial = substr(addslashes(str_replace(" ","",$Row['0']['2'])),0,1);
$sinitial = substr(addslashes(str_replace(" ","",$Row['0']['3'])),0,1);
$username = strtolower($finitial).strtolower($sinitial).rand(999,9999);
try {
$sthUser->bindParam(':username', $username);
$sthUser->execute();
$Row = $sthUser->fetch(PDO::FETCH_ASSOC);
}catch (PDOException $e) {
print $e->getMessage();
}
}while(!empty($Row));
$hashedPassword = create_hash($Row['0']['1']);
$usertype = 'Student';
$sth->bindParam($i++, $username);
$sth->bindParam($i++, $usertype);
$sth->bindParam($i++, $hashedPassword);
}
try {
$sth->execute();
}catch (PDOException $e) {
print $e->getMessage();
}
Found the answer here - It seems that bindParam inside the loop binds by reference and is only evaluated at execute statement so it always contains the last bound value for each field.
Changing it to bindValue worked.
All queries execute successfully, when I check table in MySQL row inserted successfully without any error, but lastInsertId() returns 0. why?
My code:
// queries executes successfully, but lastInsetId() returns 0
// the menus table has `id` column with primary auto_increment index
// why lastInsertId return 0 and doesn't return actual id?
$insertMenuQuery = "
SELECT #rght:=`rght`+2,#lft:=`rght`+1 FROM `menus` ORDER BY `rght` DESC limit 1;
INSERT INTO `menus`(`parent_id`, `title`, `options`, `lang`, `lft`, `rght`)
values
(:parent_id, :title, :options, :lang, #lft, #rght);";
try {
// menu sql query
$dbSmt = $db->prepare($insertMenuQuery);
// execute sql query
$dbSmt->execute($arrayOfParameterOfMenu);
// menu id
$menuId = $db->lastInsertId();
// return
return $menuId;
} catch (Exception $e) {
throw new ForbiddenException('Database error.' . $e->getMessage());
}
With PDO_MySQL we must use
$DB->setAttribute(PDO::ATTR_EMULATE_PREPARES,TRUE); // there are other ways to set attributes. this is one
so that we can run multiple queries like:
$foo = $DB->prepare("SELECT * FROM var_lst;INSERT INTO var_lst (value) VALUES ('durjdn')");
but sadly, doing so relieves the $DB from returning the correct insert id. You would have to run them separately to be able to retrieve the insert id. This returns the correct insert id:
$DB->setAttribute(PDO::ATTR_EMULATE_PREPARES,TRUE);
$foo = $DB->prepare("INSERT INTO var_lst (value) VALUES ('durjdn')");
$foo->execute();
echo $DB->lastInsertId();
but this won't:
$DB->setAttribute(PDO::ATTR_EMULATE_PREPARES,TRUE);
$foo = $DB->prepare("SELECT * FROM var_lst;INSERT INTO var_lst (value) VALUES ('durjdn')");
$foo->execute();
echo $DB->lastInsertId();
and this won't even run the two queries:
$DB->setAttribute(PDO::ATTR_EMULATE_PREPARES,FALSE); // When false, prepare() returns an error
$foo = $DB->prepare("SELECT * FROM var_lst;INSERT INTO var_lst (value) VALUES ('durjdn')");
$foo->execute();
echo $DB->lastInsertId();
Place $dbh->lastInsertId(); Before $dbh->commit() and After $stmt->execute();
I have an array like this which contains a lot of row and I need to insert it into a MySQL database using PDO
array(3067) {
[0]=>
array(2) {
["order"]=>
string(7) "2854811"
["consignment"]=>
string(0) ""
}
[1]=>
array(2) {
["blah"]=>
string(7) "2854811"
["whatever"]=>
string(2) "someval"
}
[4]=>
array(2) {
["blah"]=>
string(7) "2864412"
["whatever"]=>
string(0) ""
}
I have tried various combinations of suggestions made on here but each suggestion gets a different error message
php PDO insert batch multiple rows with placeholders
PDO MySQL: Insert multiple rows in one query
I have tried this
$db->beginTransaction();
$stmt = $db->prepare("INSERT INTO mytable (column1, column2) VALUES (:blah, :whatever)");
foreach($test as $insertRow){
// now loop through each inner array to match binded values
foreach($insertRow as $column => $value){
$stmt->bindParam(":{$column}", $value);
$stmt->execute();
}
}
$db->commit();
but i get this error message
Uncaught exception 'PDOException' with message 'SQLSTATE[HY093]:
Invalid parameter number: number of bound variables does not match
number of tokens
and I've also tried
$sql = 'INSERT INTO mytable (blah, whatever) VALUES ';
$insertQuery = array();
$insertData = array();
$n = 0;
foreach ($input as $row) {
$insertQuery[] = '(:blah' . $n . ', :whatever' . $n . ')';
$insertData['blah' . $n] = $row['blah'];
$insertData['whatever' . $n] = $row['whatever'];
$n++;
}
if (!empty($insertQuery)) {
$sql .= implode(', ', $insertQuery);
$stmt = $db->prepare($sql);
$stmt->execute($insertData);
}
but i get this error message which makes no sense as each length of 'blah' are the same
Uncaught exception 'PDOException' with message 'SQLSTATE[22001]:
String data, right truncated: 1406 Data too long for column
'order_number' at row 1625'
How can i get my array to insert into the database? I'm not fussy if i have to execute a load of times or just once as long as I can get it to insert.
EDIT
What I am trying to do is read in a text file and insert it into an array which works perfectly so all i'm left with is an associative array with about 3000 rows and they each contain a field called 'blah' and 'whatever'.
After I get my array, i need to insert it into a MySQL database
CREATE TABLE IF NOT EXISTS `tracker` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`blah` varchar(8) NOT NULL,
`whatever` varchar(25) NOT NULL,
`input_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
I should end up with about 3000 rows that was inserted from my array.
I hope this makes sense. If not I'll add some more
I believe you almost had it with this example:
$db->beginTransaction();
$stmt = $db->prepare("INSERT INTO mytable (column1, column2) VALUES (:blah, :whatever)");
foreach($test as $insertRow){
// now loop through each inner array to match binded values
foreach($insertRow as $column => $value){
$stmt->bindParam(":{$column}", $value);
$stmt->execute();
}
}
$db->commit();
The problem you are running into is you are calling execute() before you have bound the proper number of parameters. Instead, you need to bind all of your parameters first, then call execute().
$db->beginTransaction();
$stmt = $db->prepare("INSERT INTO mytable (column1, column2) VALUES (:blah, :whatever)");
foreach($test as $insertRow){
// now loop through each inner array to match binded values
foreach($insertRow as $column => $value){
$stmt->bindParam(":{$column}", $value);
}
}
// NOW DO EXECUTE
$stmt->execute();
$db->commit();
EDIT
In response to your comment, it's hard to tell exactly what you are trying to accomplish, but if you are only receiving one record, then it is because of what Gerald brought up, these are separate queries to all be transacted at once. Take a look at this revision:
// Start Transaction
$db->beginTransaction();
// Insert each record
foreach($test as $insertRow){
// Prepare statement
$stmt = $db->prepare("INSERT INTO mytable (column1, column2) VALUES (:blah, :whatever)");
// now loop through each inner array to match binded values
foreach($insertRow as $column => $value){
$stmt->bindValue(":{$column}", $value);
}
// Execute statement to add to transaction
$stmt->execute();
// Clear statement for next record (not necessary, but good practice)
$stmt = null;
}
// Commit all inserts
$db->commit();