I'm converting an app from native mysqli calls to PDO. Running into an error when attempting to insert a row into a table with a foreign key constraint.
Note: this is a simplified test case and should not be copy/pasted into a production environment.
Info PHP 5.3, MySQL 5.4
First, here are the tables:
CREATE TABLE `z_one` (
`customer_id` int(10) unsigned NOT NULL DEFAULT '0',
`name_last` varchar(255) DEFAULT NULL,
`name_first` varchar(255) DEFAULT NULL,
`dateadded` datetime DEFAULT NULL,
PRIMARY KEY (`customer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `z_one` VALUES (1,'Khan','Ghengis','2014-12-17 10:43:01');
CREATE TABLE `z_many` (
`order_id` varchar(15) NOT NULL DEFAULT '',
`customer_id` int(10) unsigned DEFAULT NULL,
`dateadded` datetime DEFAULT NULL,
PRIMARY KEY (`order_id`),
KEY `order_index` (`customer_id`,`order_id`),
CONSTRAINT `z_many_ibfk_1` FOREIGN KEY (`customer_id`) REFERENCES `z_one` (`customer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Or if you prefer,
mysql> describe z_one;
+-------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+-------+
| customer_id | int(10) unsigned | NO | PRI | 0 | |
| name_last | varchar(255) | YES | | NULL | |
| name_first | varchar(255) | YES | | NULL | |
| dateadded | datetime | YES | | NULL | |
+-------------+------------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
mysql> describe z_many;
+-------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+-------+
| order_id | varchar(15) | NO | PRI | | |
| customer_id | int(10) unsigned | YES | MUL | NULL | |
| dateadded | datetime | YES | | NULL | |
+-------------+------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
Next, here is the query:
$order_id = '22BD24';
$customer_id = 1;
try
{
$q = "
INSERT INTO
z_many
(
order_id,
customer_id,
dateadded
)
VALUES
(
:order_id,
:customer_id,
NOW()
)
";
$stmt = $dbx_pdo->prepare($q);
$stmt->bindValue(':order_id', $order_id, PDO::PARAM_STR);
$stmt->bindValue(':customer_id', $customer_id, PDO::PARAM_INT);
$stmt->execute();
} catch(PDOException $err) {
// test case only. do not echo sql errors to end users.
echo $err->getMessage();
}
This results in the following PDO error:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
'22BD24' for key 'PRIMARY'
The same query works fine when handled by mysqli. Why is PDO rejecting the INSERT with a 'duplicate entry' message when there aren't any duplicates found?
Since not all code is available (from php side) just in case your query is in some sort of loop the quickest (and perhaps partly) solution to this is the following:
$order_id = '22BD24';
$customer_id = 1;
try {
$q = "INSERT INTO `z_many` (`order_id`,`customer_id`,`dateadded`)
VALUES (:order_id,:customer_id,NOW())
ON DUPLICATE KEY UPDATE `dateadded`=NOW()";
$stmt = $dbx_pdo->prepare($q);
$stmt->bindValue(':order_id', $order_id, PDO::PARAM_STR);
$stmt->bindValue(':customer_id', $customer_id, PDO::PARAM_INT);
$stmt->execute();
} catch(PDOException $err) {
// test case only. do not echo sql errors to end users.
echo $err->getMessage();
}
I've copied the SQL schema you provided on my mysql DB and added script code, but with PDO initialization at the start:
$dbx_pdo = new PDO('mysql:host=127.0.0.1;dbname=test12;charset=utf8','root','');
.. and it worked fine, but my setup is php 5.5.9 and mysql 5.6.16
So I think your code executes twice and maybe its inside of a transaction, so you get rollback. Need to know more context
Please REMOVE default for primary key column. Also use the construction
INSERT INTO (`field1`, `field2`, `...`) values (val1, val2, val3);
If you tell what default value is for inserts - some mysql versions can see errors when inserting. Thats why you should use auto-increment or dont use default value at all.
Just a shot in the dark. I use PDO with ORACLE PL/SQL only with bindParam(). And have a look at the forth parameter ,15 if it is a PARAM_STR value. So try this, hope it helps.
$stmt->bindParam(':order_id', $order_id, PDO::PARAM_STR, 15);
$stmt->bindParam(':customer_id', $customer_id, PDO::PARAM_INT);
This 15 descibes the (bufffer-)length from order_id in your table.
Related
My problem:
Error: Duplicate entry '2' for key 'cnt_2'
My table:
CREATE TABLE UploadLog(
id int(11) NOT NULL AUTO_INCREMENT,
date text NOT NULL DEFAULT '',
cnt int(11) NOT NULL DEFAULT 0,
UNIQUE INDEX (id, cnt),
PRIMARY KEY (id)
)
My php code:
$date1=date("Y-m-d");
$sql = "SELECT * FROM UploadLog WHERE date='$date1'";
$result = $conn->query($sql);
$row = $result->fetch_assoc();
$dateindb=$row["cnt"];
if ($dateindb==0) {$dateindb=1;};
$sql = "INSERT INTO UploadLog (date, cnt) VALUES ('$date1', '$dateindb') ON DUPLICATE KEY UPDATE date=date, cnt=cnt+1";
if (mysqli_query($conn, $sql)) {} else {echo mysqli_error($conn)};
Data in MYSQL table:
+------+------------+-----+
| id | date | cnt |
+------+------------+-----+
| 1 | 2015-04-16 | 3 |
| 2 | 2015-04-17 | 2 |
| 3 | 2015-04-18 | 1 |
+------+------------+-----+
Current date = 2015-04-18.
Edited:
Command:
DESCRIBE DateLog
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| date | text | NO | | NULL | |
| cnt | int(11) | NO | UNI | 0 | |
+-------+---------+------+-----+---------+----------------+
Command:
SHOW CREATE TABLE UploadLog
UploadLog | CREATE TABLE `UploadLog` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` text NOT NULL,
`cnt` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
UNIQUE KEY `cnt_2` (`cnt`),
KEY `cnt` (`cnt`),
KEY `id_2` (`id`),
KEY `cnt_3` (`cnt`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1
I cannot explain where the error come from, but I expect, it is caused by different MySQL versions.
But first off all I would recommend to change the date column from text to char(10). This is because the content have a very defined length.
After that the table definition will look like this:
CREATE TABLE DateLog
(
id INT(11) NOT NULL AUTO_INCREMENT,
date CHAR(10) NOT NULL,
cnt INT DEFAULT 0 NOT NULL,
UNIQUE INDEX (id, date, cnt),
PRIMARY KEY (id)
);
To get your code working you have just to add the id into the insert query. For that your code will change a little bit to:
$date1 = date("Y-m-d");
$sql = "SELECT * FROM DateLog WHERE date='$date1'";
$result = $conn->query($sql);
$row = $result->fetch_assoc();
// Check if entry for the current date already exists
if (null === $row) {
// If not, set id to NULL
$id = 'NULL';
$dateindb = 1;
} else {
// If it exist, use it
$id = $row["id"];
$dateindb = $row["cnt"];
}
// Insert with id
$sql = "INSERT INTO DateLog (id, date, cnt) VALUES ($id,'$date1', '$dateindb') ON DUPLICATE KEY UPDATE cnt=cnt+1";
if (mysqli_query($conn, $sql)) {
} else {
echo mysqli_error($conn);
}
To simplify the situation, there is no need for an auto increment primary key, because you can use the date column as one. From this it follows that you also don’t need an unique index, too.
So simply change your Table definition to:
CREATE TABLE DateLog
(
date CHAR(10) PRIMARY KEY NOT NULL,
cnt INT DEFAULT 1
);
After you can use the this to count the uploads:
$date1 = date("Y-m-d");
$sql = "INSERT INTO DateLog (date) VALUES ('$date1') ON DUPLICATE KEY UPDATE cnt=cnt+1";
if (mysqli_query($conn, $sql)) {
} else {
echo mysqli_error($conn);
}
Simple, or not?
Hope this helps.
The error is pretty obvious... you are trying to insert duplicate values in a column, defined as unique...
cnt=cnt+21 gives you 2, which you already have in your table.
The problem is the field cnt_2, which is UNIQUE. That means it can't have the same value for two different registers. You aren't setting it a value in the INSERT query, so it takes the default value, which already exist in another register.
I was trying to create a new table with the following PHP/mysql code snippet:
$query = "
CREATE TABLE IF NOT EXISTS :user (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
username CHAR(24),
summonername VARCHAR(16),
password VARCHAR(16),
region CHAR(3),
lvl INT(2) DEFAULT '0',
maxlvl INT(2),
status VARCHAR(20),
enabled INT(1) DEFAULT '1',
priority INT(1) DEFAULT '0',
note VARCHAR(150)
)
";
$query_params = array (
':user' => $user,
);
If I execute this one, I get some syntax error:
Failed: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''testuser' ( id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, use' at line 1
It is weird because I checked the syntax according to the official documentation (http://dev.mysql.com/doc/refman/5.1/de/create-table.html) and also some user-made examples.
Also, is there a way to set the default encoding of the table to utf8 (not for every single row, but global)?
I would be happy if someone can tell me how to fix the syntax errors there,
You cannot use parameters for metadata. Nor should you be creating tables per user. Either add a field to hold the user, or sanitize the table name yourself.
With reference to my comment above, and by way of example...
CREATE TABLE my_table(i INT(4) ZEROFILL NOT NULL PRIMARY KEY);
INSERT INTO my_table VALUES (1),(9),(10),(99),(100),(999),(1000),(9999),(10000);
SELECT * FROM my_table;
+-------+
| i |
+-------+
| 0001 |
| 0009 |
| 0010 |
| 0099 |
| 0100 |
| 0999 |
| 1000 |
| 9999 |
| 10000 |
+-------+
please try this query in create table
CREATE TABLE IF NOT EXISTS `user` (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
username CHAR(24),
summonername VARCHAR(16),
password VARCHAR(16),
region CHAR(3),
lvl INT(2) DEFAULT '0',
maxlvl INT(2),
status VARCHAR(20),
enabled INT(1) DEFAULT '1',
priority INT(1) DEFAULT '0',
)
The following queries use 80% or more CPU and can take more than 1 minute to complete.
My question: Is there anything wrong with my queries that would cause CPU usage like that? Can I decrease CPU usage and query time by optimizing the MySQL server conf?
Query 1 (loan_history contains 2.6 million records)
SELECT officer, SUM(balance) as balance
FROM loan_history
WHERE bank_id = '1'
AND date ='2013-07-04'
AND officer IS NOT NULL
AND officer <> ''
GROUP BY officer
ORDER BY officer;
Query 2 (loan_history contains 2.6 million records)
SELECT SUM(weighted_interest_rate) as total
FROM (SELECT balance, tmp1.balance_sum,
(balance / tmp1.balance_sum * interest_rate) as weighted_interest_rate
FROM loan_history,
(SELECT SUM(balance) balance_sum FROM loan_history
WHERE date = '2013-07-04'
AND bank_id = '1') as tmp1
WHERE date = '2013-07-04'
AND bank_id = '1') tmp2
Table information:
CREATE TABLE `loan_history` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`bank_id` int(11) DEFAULT NULL,
`loan_purpose_id` int(11) DEFAULT NULL,
`date` date NOT NULL,
`credit_grade` varchar(5) COLLATE utf8_unicode_ci DEFAULT NULL,
`interest_rate` decimal(5,2) NOT NULL,
`officer` varchar(5) COLLATE utf8_unicode_ci DEFAULT NULL,
`balance` decimal(10,2) NOT NULL,
`start_date` date DEFAULT NULL,
`days_delinquent` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `IDX_9F5FE3F11C8FB41` (`bank_id`),
KEY `IDX_9F5FE3F6F593857` (`loan_purpose_id`),
KEY `date` (`date`),
KEY `credit_grade` (`credit_grade`),
KEY `officer` (`officer`),
KEY `start_date` (`start_date`),
KEY `days_delinquent` (`days_delinquent`),
KEY `interest_rate` (`interest_rate`),
KEY `balance` (`balance`),
CONSTRAINT `FK_9F5FE3F11C8FB41` FOREIGN KEY (`bank_id`) REFERENCES `bank` (`id`),
CONSTRAINT `FK_9F5FE3F6F593857` FOREIGN KEY (`loan_purpose_id`) REFERENCES `loan_purpose` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2630634 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Query 1 EXPLAIN:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
| 1 | SIMPLE | loan_history | index_merge | IDX_9F5FE3F11C8FB41,date,officer | date,IDX_9F5FE3F11C8FB41 | 3,5 | NULL | 4829 | Using intersect(date,IDX_9F5FE3F11C8FB41); Using where; Using temporary; Using filesort |
Query 2 EXPLAIN:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 8236 |
| 2 | DERIVED | <derived3> | system | NULL | NULL | NULL | NULL | 1 |
| 2 | DERIVED | loan_history | index_merge | IDX_9F5FE3F11C8FB41,date | date,IDX_9F5FE3F11C8FB41 | 3,5 | NULL | 4829 | Using intersect(date,IDX_9F5FE3F11C8FB41); Using where; Using index |
| 3 | DERIVED | loan_history | index_merge | IDX_9F5FE3F11C8FB41,date | date,IDX_9F5FE3F11C8FB41 | 3,5 | NULL | 4829 | Using intersect(date,IDX_9F5FE3F11C8FB41); Using where; Using index |
My.cnf file:
default-storage-engine=MyISAM
interactive_timeout=300
key_buffer_size=256M
key_cache_block_size=4096
max_heap_table_size=128M
max_join_size=1000000000
max_allowed_packet=32M
open_files_limit=4096
query_cache_size=256M
query_cache_limit=10240M
query_cache_type=1
table_cache=256
thread_cache_size=100
tmp_table_size=128M
wait_timeout=7800
max_user_connections=50
join_buffer_size=256K
sort_buffer_size=4M
read_rnd_buffer_size=1M
innodb_open_files=300
innodb_log_file_size=256M
innodb_log_buffer_size=8M
innodb_file_per_table=1
innodb_additional_mem_pool_size=20M
innodb_flush_log_at_trx_commit=0
innodb_flush_method=O_DIRECT
innodb_support_xa=0
innodb_thread_concurrency=0
innodb_buffer_pool_size=3000M
The sum() and GROUP BY from the first query could be taking some time but I don't think there is much you can do there.
In the second query your FROM (SELECT.... is probably hitting the system pretty hard, I would recommend turning
(SELECT balance, tmp1.balance_sum,
(balance / tmp1.balance_sum * interest_rate) as weighted_interest_rate
FROM loan_history,
(SELECT SUM(balance) balance_sum FROM loan_history
WHERE date = '2013-07-04'
AND bank_id = '1') as tmp1
WHERE date = '2013-07-04'
AND bank_id = '1')
into a view or figuring out how to do it with JOINs
Please tell us how much does exactly each one take. More than 1 minute each isn't that much indicative.
Any how, From MySQL manual
When tuning a MySQL server, the two most important variables to configure are key_buffer_size and table_cache. You should first feel confident that you have these set appropriately before trying to change any other variables.
Also, take a look here.
As for optimization, first try a composite index.
I am about 98% sure this query has been working properly on mysql 5.1. We have upgraded one of our machines to mysql 5.6 and I am running an update and its getting a database error with this query:
UPDATE diagnostic
LEFT JOIN contact ON diagnosticdata_suppliercontact = contact_id
SET diagnosticdata_suppliercontact = NULL
WHERE (!contactdata_issupplier) OR (contact.contact_id IS NULL);
However the table schema allows it to be NULL. Does anyone know of any problems or changes that happened in mysql 5.6 that could cause us grief?
Here is a description of the table:
mysql> describe diagnostic;
+--------------------------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------------------+-------------+------+-----+---------+----------------+
| diagnostic_id | int(11) | NO | PRI | NULL | auto_increment |
| diagnostic_time | int(15) | NO | | NULL | |
| diagnostic_user | int(15) | NO | | NULL | |
| diagnosticdata_time | int(15) | NO | | NULL | |
| diagnosticdata_user | int(15) | NO | | NULL | |
| diagnosticdata_name | varchar(50) | NO | | NULL | |
| diagnosticdata_suppliercontact | int(11) | YES | MUL | NULL | |
+--------------------------------+-------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)
ERROR 1048 (23000): Column 'diagnosticdata_suppliercontact' cannot be null
CREATE STATEMENT IS HERE TO there are triggers:
delimiter $$
CREATE TABLE `diagnostic` (
`diagnostic_id` int(11) NOT NULL AUTO_INCREMENT,
`diagnostic_time` int(15) NOT NULL,
`diagnostic_user` int(15) NOT NULL,
`diagnosticdata_time` int(15) NOT NULL,
`diagnosticdata_user` int(15) NOT NULL,
`diagnosticdata_name` varchar(50) NOT NULL,
`diagnosticdata_suppliercontact` int(11) DEFAULT NULL,
PRIMARY KEY (`diagnostic_id`),
KEY `diagnostic_suppliercontact` (`diagnosticdata_suppliercontact`) USING BTREE,
CONSTRAINT `fk_diagnosticdata_suppliercontact_contact` FOREIGN KEY (`diagnosticdata_suppliercontact`) REFERENCES `contact` (`contact_id`)
) ENGINE=InnoDB AUTO_INCREMENT=188 DEFAULT CHARSET=utf8$$
CREATE
DEFINER=`dotuser`#`localhost`
TRIGGER `ezymerged`.`diagnostic_insert`
AFTER INSERT ON `ezymerged`.`diagnostic`
FOR EACH ROW
BEGIN
REPLACE INTO ezymerged_history.diagnostic
SELECT diagnostic.* FROM ezymerged.diagnostic
WHERE diagnostic_id=NEW.diagnostic_id;
END
$$
CREATE
DEFINER=`dotuser`#`localhost`
TRIGGER `ezymerged`.`diagnostic_update`
AFTER UPDATE ON `ezymerged`.`diagnostic`
FOR EACH ROW
BEGIN
REPLACE INTO ezymerged_history.diagnostic
SELECT diagnostic.* FROM ezymerged.diagnostic
WHERE diagnostic_id=NEW.diagnostic_id;
END
$$
While the diagnosticdata_suppliercontact allows for NULLs the constraint:
CONSTRAINT `fk_diagnosticdata_suppliercontact_contact`
FOREIGN KEY (`diagnosticdata_suppliercontact`)
REFERENCES `contact` (`contact_id`)
will take precedence, and requires the value in diagnosticdata_suppliercontact to have a matching value in the contact_id field in the contact table.
My problem is the following:
I have two tables; persons and teams, I want to select all the persons with role_id = 2, that exist in persons but not in teams.
Table teams stores the hashes for the team leader who can only lead one team at a time. When creating teams, I just want to show administrators the people who is not currently leading a team, basically exclude all the ones who are already leaders of any given team.
My structure is as follows:
mysql> desc persons;
+-------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| firstname | varchar(9) | YES | | NULL | |
| lastname | varchar(10) | YES | | NULL | |
| role_id | int(2) | YES | | NULL | |
| hash | varchar(32) | NO | UNI | NULL | |
+-------------+-------------+------+-----+---------+-------+
mysql> desc teams;
+--------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| leader | varchar(32) | NO | | NULL | |
+--------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
My current SQL is as follows:
SELECT CONCAT( `persons`.`firstname` ," ", `persons`.`lastname` ) AS `manager`,
`hash` FROM `persons`
WHERE `persons`.`role_id` =2 AND `persons`.`hash` !=
(SELECT `leader` FROM `teams` );
The latter SQL Query works when the table teams only has 1 record, but as soon as I add another one, MySQL complaints about the subquery producing two records.
In the WHERE Clause, instead of subqueries I've also tried the following:
WHERE `persons`.`role_id` = 2 AND `persons`.`hash` != `teams`.`leader`
but then it complaints about column leader not existing in table teams
I was also thinking about using some kind of inverse LEFT JOIN, but I haven't been able to come up with an optimal solution.
Any help is greatly appreciated!
Thanks
P.S.: Here is the SQL statements should you want to have a scenario similar to mine:
DROP TABLE IF EXISTS `teams`;
CREATE TABLE IF NOT EXISTS `teams` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`leader` varchar(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
INSERT INTO `teams` (`id`, `name`, `leader`) VALUES
(1, 'Team 1', '406a3f5892e0fcb22bfc81ae023ce252'),
(2, 'Team 2', 'd0ca479152996c8cabd89151fe844e63');
DROP TABLE IF EXISTS `persons`;
CREATE TABLE IF NOT EXISTS `persons` (
`firstname` varchar(9) DEFAULT NULL,
`lastname` varchar(10) DEFAULT NULL,
`role_id` int(2) DEFAULT NULL,
`hash` varchar(32) NOT NULL,
PRIMARY KEY `hash` (`hash`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `persons` (`firstname`, `lastname`, `role_id`,`hash`) VALUES
('John', 'Doe', 2, '406a3f5892e0fcb22bfc81ae023ce252'),
('Jane', 'Doe', 2, 'd0ca479152996c8cabd89151fe844e63'),
('List', 'Me', 2, 'fbde2c4eeee7f455b655fe4805cfe66a'),
('List', 'Me Too', 2, '6dee2c4efae7f452b655abb805cfe66a');
You don't need a subquery to do that. A LEFT JOIN is enough:
SELECT
CONCAT (p.firstname, " ", p.lastname) AS manager
, p.hash
FROM persons p
LEFT JOIN teams t ON p.hash = t.leader
WHERE
p.role_id = 2
AND t.id IS NULL -- the trick
I think you want an IN clause.
SELECT CONCAT( `persons`.`firstname` ," ", `persons`.`lastname` ) AS `manager`,
`hash` FROM `persons`
WHERE `persons`.`role_id` =2 AND `persons`.`hash` NOT IN
(SELECT `leader` FROM `teams` );
As pointed out, this is not optimal. You may want to do a join instead.