I'll use the example from SQLite help page about foreign key constrains:
Let's build 2 tables in SQLite:
CREATE TABLE artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
CREATE TABLE track(
trackid INTEGER PRIMARY KEY,
trackname TEXT,
trackartist INTEGER,
CONSTRAINT fk
FOREIGN KEY(trackartist) REFERENCES artist(artistid))
ON UPDATE CASCADE ON DELETE CASCADE
);
Let's add 2 records from SQLite command:
sqlite> pragma foreign_keys = ON;
sqlite> INSERT INTO artist(artistid, artistname) VALUES(null, 'Bing Crosby');
sqlite> INSERT INTO track(trackid, trackname, trackartist) VALUES(null, 'White Christmas', 1);
sqlite> SELECT * FROM artist;
1|Bing Crosby
sqlite> SELECT * FROM track;
1|White Christmas|1
Now, while the command tool does NOT allow (correctly) an insert of:
sqlite> INSERT INTO track(trackid, trackname, trackartist) VALUES(null, 'Another Track', 9);
Error: FOREIGN KEY constraint failed
php on the other hand DOES allow (incorrectly) this:
$dbh = new \PDO('sqlite:test.sq3');
$dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
try {
if (!$dbh->beginTransaction())
throw new \Exception('Could not started a transaction!');
//$dbh->exec('PRAGMA foreign_keys = ON;');
$query = $dbh->prepare('PRAGMA foreign_keys = ON;');
$query->execute();
//$dbh->exec("INSERT INTO track(trackid, trackname, trackartist) VALUES(null, 'Another Track', 9);");
$query = $dbh->prepare("INSERT INTO track(trackid, trackname, trackartist) VALUES(null, 'White Christmas', 9);");
$query->execute();
$dbh->commit();
} catch (\Exception $e) {
$dbh->rollback();
}
See the violation:
sqlite> SELECT * FROM track;
1|White Christmas|1
2|Another Track|9
Any idea? Thank you!
The documentation says:
This pragma is a no-op within a transaction; foreign key constraint enforcement may only be enabled or disabled when there is no pending BEGIN or SAVEPOINT.
Related
I have a strange problem with PDO not throwing an exception when a duplicate value is inserted. In this case I did expect an error.
The relevant code:
try
{
$db_conn = new PDO("mysql:host=".$config["database"]["hostname"].";charset=utf8", $config["database"]["username"], $config["database"]["password"], []);
$db_conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db_conn->exec(file_get_contents("some_file_with_data.sql");
}
catch(Exception $e)
{
// PDOException extends RuntimeException extends Exception so exceptions should be catched here
// however for the duplicate key entry it will not throw an exception
}
The file with SQL data contains multiple inserts like this:
INSERT INTO `a` (`b`, `c`) VALUES
(1, 1),
(2, 2),
(3, 2);
INSERT INTO `a` (`b`, `c`) VALUES
(1, 1);
The field b in table a is set to being the primary key. When I insert the exact same data in the exact same structure using phpMyAdmin I get this error: #1062 - Duplicate entry '65533' for key 'PRIMARY'
Why does PDO not throw an error in this case? Even when I set the error mode to exception?
Edit:
This is the table structure used for this specific table
CREATE TABLE IF NOT EXISTS `a` (
`b` smallint(5) unsigned NOT NULL,
`c` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Update 2018: DEVs do not consider this a bug, but intended behaviour.
So, PHP-Users have to live with that, Report is closed for any future questions...
This has often been reported as bug with PDO: https://bugs.php.net/bug.php?id=61613
It will only throw an exception if the FIRST Statement is invalid. If the first statement runs smooth, you won't get any error - And your first statement is valid:
INSERT INTO `a` (`b`, `c`) VALUES
(1, 1),
(2, 2),
(3, 2);
as a workaround - or according to user deleted the right way of doing it - you need to process the rowsets one-by-one (taken from the bug reports comments):
$pdo->beginTransaction();
try {
$statement = $pdo->prepare($sql);
$statement->execute();
while ($statement->nextRowset()) {/* https://bugs.php.net/bug.php?id=61613 */};
$pdo->commit();
} catch (\PDOException $e) {
$pdo->rollBack();
throw $e;
}
I am using PHP PDO with SQLite database. This is the schema of the table I am working on:
CREATE TABLE "quests" (
`id` TEXT,
`parent` TEXT,
PRIMARY KEY(id),
FOREIGN KEY(`parent`) REFERENCES quests(id)
)
... and contains one line which has hello as id.
And next is the PHP script I am using (less unrelated debug lines):
<?php
$dbhandle = new PDO("sqlite:sutori.sqlite3");
$dbhandle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbhandle->exec("PRAGMA foreign_keys = ON;");
$sth = $dbhandle->prepare("INSERT INTO quests VALUES ( :id, :parent );");
$sth->bindValue(":id", "nouvelles", SQLITE3_TEXT);
$sth->bindValue(":parent", "hello", SQLITE3_TEXT);
$sth->execute();
die();
?>
The above script will return this SQL error:
PHP Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[23000]: Integrity constraint violation: 19 FOREIGN KEY constraint failed' in /var/www/html/sutori/sutori.php:56
But if I remove the data_type argument from bindValue of the foreign key field as below:
$sth->bindValue(":id", "nouvelles", SQLITE3_TEXT);
$sth->bindValue(":parent", "hello"); // SQLITE3_TEXT removed
... it works, so the INSERT is done.
I know the script would work as expected if I don't specify the data_type argument. But do you have any idea why I am getting this behavior? Is it a PDO bug/feature or is my script wrong?
Thank you for your inputs! :)
In my MySQL database I have a table "table1" with unique constraint set on column "name" - I want to prevent duplicate names.
If there's already name 'John' in table this code:
$db=new mysqli(...);
$sql="INSERT INTO table1 SET id=10,name='John'";
if(!$db->query($sql))
{
if($db->errno==1062)
{
throw new InsertNonUniqueException(...);
}
else
{
throw new InsertException(...);
}
}
should throw InsertNonUniqueException() (my own exception). Instead, it throws InsertException().
Execution of query returns false and execution enters the if() loop. Also $db->row_affected is -1 but problem is that $db->errno is always O (it should be 1062)!!! So I can't detect that my insert error was caused by violating unique key constraint on name column!
I don't know why mysqli does not return 1062 code when unique key constraint violation occurs!
I can't leave a comment, thus going to ask you here.
Please provide the result of SHOW CREATE TABLE table1;
I can't reproduce your problem using your code and next table:
CREATE TABLE `table1` (
`name` varchar(11) COLLATE utf8_unicode_ci NOT NULL,
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Am I the only one around here that thinks you have an error in your SQL syntax?.. There is no room for SET in INSERT INTO, because you can only use SET in UPDATE statements (assuming you habe MySQL in version 5.5 or below).
INSERT INTO syntax is like the following (as described in the docs):
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name [(col_name,...)]
SELECT ...
[ ON DUPLICATE KEY UPDATE col_name=expr, ... ]
OR
INSERT INTO tbl_temp2 (fld_id)
SELECT tbl_temp1.fld_order_id
FROM tbl_temp1 WHERE tbl_temp1.fld_order_id > 100;
Try it like this:
<?php
$sql="INSERT INTO table1 (id, name) VALUES ('10', 'John')";
...
step 1
make sure that the table has a unique key
SHOW CREATE TABLE table1
expected result
CREATE TABLE `table1` (
`id` INT(11) default NULL,
`name` varchar(11) COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci
if there is UNIQUE KEY name (name) we have a unique key
step 2
try to change your code
$db = new mysqli(...);
// first insert
if( !$db->query("INSERT INTO table1 (id, name) VALUES (10, 'John')") ) {
throw new Exception($db->error);
}
// second insert (for me raise: Duplicate entry 'John' for key 'name')
if( !$db->query("INSERT INTO table1 (id, name) VALUES (11, 'John')") ) {
throw new Exception($db->error);
}
Please, try these two steps
Side note: if you have name and id as duplicates, only the first duplicate encountered will be returned in the message.
The only issue i have with your code is that:
having setup your table and columns.
I setup a unique index on the table. I did .. stuff on a two column table that ensure it works.
You missed the 'new'
keyword when you 'throw exceptions'.
this is the only error with your posted code that i could find.
i.e: throw new Exception('Division by zero.'); // example taken from PHP manual.
I have database with 5 tables
students PK : ID -> anum, first, last
studentinfo PK/FK : ID -> why, student_commenets, finished, aidyear
Times PK/FK : ID -> signintime, counselor_start_time,
additional_time, finish_time
counselor PK/FK : ID -> firstcounselor, secondcounselor, thirdcounselor
Comments PK/FK : ID -> counselorcomments, additional_commenets
I have a page called signinpage.php
on that page I have to write to three different tables (student, studentinfo, and time)
My code is as fallows :
if (empty($errors) === true)
{
include('core/queries/inserts.updates.selects/students.php');
include('core/queries/inserts.updates.selects/studentinfo.php');
include('core/queries/inserts.updates.selects/signintime.php');
$dbh = null;
header('location: signedin.php');
exit();
}
each of the files are actual insert queries. (if you yall need to see them I will update this post)
The error I am having is :
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or
update a child row: a foreign key constraint fails (test.times,
CONSTRAINT times_ibfk_2 FOREIGN KEY (id) REFERENCES students
(id) ON DELETE CASCADE ON UPDATE CASCADE)
To add on to this, the first query (the students.php and the second query studentinfo.php)
are inserting just fine. Same ID, the problem occurs with the signintime inserting into table : times.
In phpmyadmin both tables (studentinfo and times) are configured alike with both have cascade on delete and update to the original table (student) since the student him/her starts the session (which is the PK ID).
How can I solved this error?
Edit :
<?php
require('core/init.php');
try
{
$null = NULL;
$query = $dbh->prepare("INSERT INTO `times` (signintime) VALUES (:signintime)");
$query->bindParam(':signintime' , $null);
$query->execute();
}
catch (PDOException $e)
{
error_log($e->getMessage());
die($e->getMessage());
}
?>
Your table design looks wrong to me. I'm assuming there can be multiple entries in the times table for each row in the students table. In that case, you would need the following columns in times:
id - PK
student_id - FK
signintime
counselor_start_time
additional_time
finish_time
Then each row for a particular student would have the same student_id value, but different id values.
The following statements and example is different from the tables you have mentioned but the thought is still the same.
The reason why the error was generated is because you are trying to insert a value on a child table in which that value is not yet present on the parent table. The child table means that it is dependent on the other table (which is the Parent).
To explain further, consider the following schema,
CREATE TABLE StudentList
(
ID INT PRIMARY KEY,
NAme VARCHAR(50)
);
CREATE TABLE AddressList
(
StudentID INT,
Address VARCHAR(50),
CONSTRAINT tb_fk FOREIGN KEY (StudentID)
REFERENCES StudentList(ID)
);
INSERT INTO StudentList VALUES (1, 'Jon');
INSERT INTO StudentList VALUES (2, 'Skeet');
INSERT INTO AddressList VALUES (1, 'Hello');
INSERT INTO AddressList VALUES (2, 'World');
INSERT INTO AddressList VALUES (1, 'Other Address');
There are two tables: StudentList and AddressList. The table Address is the child table and which is dependent to table StudentList (also called the Parent table). The only values that are allowed to be inserted on column StudentID of table AddressList is only 1 and 2 because those are the only IDs found on table StudentList.
When you try to insert record with ID other than 1 and 2 on table Address, eg
INSERT INTO AddressList VALUES (1, 'Other Address');
it will generate an error telling that:
Cannot add or update a child row: a foreign key constraint fails
(db_2_ec2e8.addresslist, CONSTRAINT tb_fk FOREIGN KEY
(StudentID) REFERENCES studentlist (ID)):
because the value of the column StudentID being inserted on the table is not available on the parent table (StudentList).
So, I hoped that this will help you understand now.
SQLFiddle Demo
I've been reading some questions with the same problem I have but have not found the solution
Database Estructure
CREATE TABLE material (
cve_mat varchar(12),
type_mat varchar(20),
author_mat varchar(40),
status varchar(2),
primary key(cve_mat)
) ENGINE = INNODB;
CREATE TABLE users (
cve_user varchar(8),
name_user varchar(25),
lastn_user varchar(25),
email_user varchar(25),
primary key(cve_user)
)ENGINE = INNODB;
CREATE TABLE material_user (
cve_mat varchar(8),
cve_user varchar(8),
start_mat date,
end_mat date,
foreign key(cve_mat) references material(cve_mat)
ON UPDATE CASCADE
ON DELETE CASCADE,
foreign key(cve_user) references users(cve_user)
ON UPDATE CASCADE
ON DELETE CASCADE
) ENGINE = INNODB;
And when execute these instructions:
......
$query = "INSERT INTO material_user VALUES ('$cvemat','$ncontrol',NOW(),DATE_ADD(current_date, interval '$days' day))";
$result = mysql_query($query) or die(mysql_error());
if ($result) {
$query = "UPDATE material SET status = 'YES' WHERE material.cve_mat = '$cvemat'";
$result = mysql_query($query) or die(mysql_error());
if ($result){
return true;
} else {
return false;
}
} else {
return false;
}
And display this error:
Cannot add or update a child row: a foreign key constraint fails (`biblioteca/material_prestamo`, CONSTRAINT `material_prestamo_ibfk_1` FOREIGN KEY (`cve_mat`) REFERENCES `material` (`cve_mat`) ON DELETE CASCADE ON UPDATE CASCADE)
I would greatly appreciate your help!
First, the column types for the foreign key should match the referencing tables:
material_user
cve_mat varchar(8),
material
cve_mat varchar(12),
Those two column types should be equal.
Before your insert statement you can perform some debugging:
SELECT * FROM material WHERE cve_mat='$cvemat'
If all seems okay, you can run this to find out more details:
SHOW INNODB STATUS;
You have foreign key constrain so you need to execute your second query first then your fisrt query second. It may be help you
$query = "UPDATE material SET status = 'YES' WHERE material.cve_mat = '$cvemat'";
$result = mysql_query($query) or die(mysql_error());
if ($result) {
$query = "INSERT INTO material_user VALUES ('$cvemat','$ncontrol',NOW(),DATE_ADD(current_date, interval '$days' day))";
$result = mysql_query($query) or die(mysql_error());