PHP MySQL AUTO_INCREMENT with INSERT INTO .. ON DUPLICATE KEY - php

I have searched for an answer for days, however I can't seem to find the right solution. Therefore, I ask the following question:
Suppose I have a table with a column ID which is an AUTO_INCREMENT field and a column Word which is unique. I run the following queries:
"INSERT IGNORE INTO Table (Word) VALUES('Test')"
"INSERT IGNORE INTO Table (Word) VALUES('Test1')"
"INSERT IGNORE INTO Table (Word) VALUES('Test2')"
"INSERT IGNORE INTO Table (Word) VALUES('Test')" //THIS ONE WILL BE IGNORED
The problem is I can't get the last $mysqli->insert_id from the last query, because it isn't inserting anything. However I need this ID which is already in the DB. therefore, I thought I should use a ON DUPLICATE KEY UPDATE statement, however this leads to the situation where AUTO_INCREMENT is skipping values, because it updates the value but ALSO increments the AUTO_INCREMENT value although this value isn't assigned to any row.
So in the end, I end up with a table like this:
ID |Word
1 |Test
2 |Test1
3 |Test2
//Trying to insert words that where already in the table..
12 |Test3
//Trying to insert words that where already in the table..
17 |Test4

My answer would be to first retrieve the id for the word from the table and only if it fails to insert it. In both cases you have the id ready.
My guess is also that it will be faster this way around since you are not creating any ignored errors in mysql.

Related

Ignore duplicate rows

I am importing some data from a csv file into MySQL and trying to ignore duplicate rows.
mysql_query("INSERT IGNORE INTO products (parent_product_url, child_product_url, swatch) VALUES ('".$row[0]."', '".$row[1]."', '".$row[2]."')");
My csv file.
polo.htm,red.htm,red.jpg
polo.htm,green.htm,green.jpg
round-neck.htm,green.htm,green.jpg
Now if I run below csv file it should ignore first three rows as they already exists in the table. It should insert only fourth row.
polo.htm,red.htm,red.jpg
polo.htm,green.htm,green.jpg
round-neck.htm,green.htm,green.jpg
v-neck.htm,red.htm,red.jpg
I prefer on duplicate key update because insert ignore ignores all errors, not just duplication errors.
Regardless of which you use, your problem is probably the lack of unique constraint/index.
You don't specify what you mean by "duplicate". Assuming you mean all the columns:
create unique index unq_products_3 on products(parent_product_url, child_product_url, swatch);
Note: there is a maximum length to the keys used for indexes, depending on the storage engine. If your columns are too long, you may need to think about other approaches.
Records are inserted again when you re-execute insert statements because the inserts are not violating any unique or primary key index. Therefore MySQL doesn't have anything to ignore.
create table products (
parent_product_url varchar(100),
child_product_url varchar(100),
swatch varchar(100)
);
-- this will enter both records
insert ignore into products values ('polo.htm', 'red.htm', 'red.jpg');
insert ignore into products values ('polo.htm', 'green.htm', 'green.jpg');
-- this will enter both records **AGAIN**
insert ignore into products values ('polo.htm', 'red.htm', 'red.jpg');
insert ignore into products values ('polo.htm', 'green.htm', 'green.jpg');
Now let's add uniqueness to parent_product_url and try again:
truncate table products;
create unique index uk_products_parent_product_url on products(parent_product_url);
insert ignore into products values ('polo.htm', 'red.htm', 'red.jpg');
insert ignore into products values ('polo.htm', 'green.htm', 'green.jpg');
This will enter only the first record. 2nd record will be ignored and a warning will be thrown. No error will be thrown.
If you desire to have a combination of the 3 columns to be unique, then you would do this (This is what Gordon Linoff has mentioned also...I am just adding more context):
alter table products drop key uk_products_parent_product_url;
create unique index uk_products_parenturl_childurl_swatch on
products(parent_product_url, child_product_url, swatch);
insert ignore into products values ('polo.htm', 'red.htm', 'red.jpg');
insert ignore into products values ('polo.htm', 'green.htm', 'green.jpg');
Now you will see only two records inserted even when you re-execute the same 2 insert statements many times.
From https://dev.mysql.com/doc/refman/5.5/en/insert.html
If you use the IGNORE keyword, errors that occur while executing the
INSERT statement are ignored. For example, without IGNORE, a row that
duplicates an existing UNIQUE index or PRIMARY KEY value in the table
causes a duplicate-key error and the statement is aborted. With
IGNORE, the row is discarded and no error occurs. Ignored errors may
generate warnings instead, although duplicate-key errors do not.
I got it solved with the help of this Answer -> Insert query check if record exists - If not, Insert it
Below is my updated query
mysql_query("INSERT INTO products (parent_product_url, child_product_url, swatch)
SELECT * FROM (SELECT '".$row[0]."', '".$row[1]."', '".$row[2]."') AS tmp
WHERE NOT EXISTS (
SELECT * FROM products WHERE parent_product_url='".$row[0]."' AND child_product_url='".$row[1]."' AND swatch='".$row[2]."'
);");

PHP MySQL Insert Into & On Duplicate Key Problems

Is my code/syntax wrong? I'm not sure what is wrong and am new to this. I have created a table in PHPMyAdmin. It has two columns. One is "id" and is the primary key/auto-increment. The other column is "steamname".
This code is supposed to take a person's online name and enter it into the database. If there is already a record, it should update it anyways with the same/latest name.
The name of the table in phpmyAdmin is names
<?php
// Capture person's name from XML file
$profile = simplexml_load_file('UrlGoesHere.Com/?xml=1', 'SimpleXMLElement', LIBXML_NOCDATA);
echo (string)$profile->steamID;
//Enter name into databse and overwrite it if same/duplicate
$con=mysqli_connect("localhost","username","password","databaseName");
// Check connection
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
// Perform queries
mysqli_query($con,"INSERT INTO names (steamname) VALUES ('$profile') ON DUPLICATE KEY UPDATE steamname = VALUES('$profile')");
mysqli_close($con);
?>
I tested this by manually changing the value of a row in "steamname" within PHPMyAdmin to "woogy" and then running this script to see if it would update that database value... nothing is happening. It should update "woogy" to the proper name.
-----------Updates---------------
Hi all. Thank you for the input. I now have my code as follows. Sorry if it's still wrong. I'm learning.
<?php
$profile = simplexml_load_file('http://steamcommunity.com/profiles/76561198006938281/?xml=1', 'SimpleXMLElement', LIBXML_NOCDATA);
echo (string)$profile->steamID;
$con=mysqli_connect("localhost","userHere","passwordHERE","DBName");
// Check connection
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
// Perform queries
mysqli_query($con,"INSERT INTO names (steamname) VALUES ('$profile') ON DUPLICATE KEY UPDATE ID = LAST_INSERT_ID(ID), steamname = VALUES(steamname)");
mysqli_close($con);
?>
This is how the database looks before running the script: (woogy should change to Chatyak)
Click image here
Now, when I run the PHP page, this is what happens to my database.
How database looks after running script
Not sure why it didn't update - and also why there is such a huge space?
Right syntax of INSERT ... ON DUPLICATE KEY would be like below, you need to mention the column name in VALUES like VALUES(column_name) instead of VALUES('$profile'). Also, you are missing the PK column ID in case of UPDATE. Cause you want to update the steamname for particular ID value.
INSERT INTO names (steamname)
VALUES ('$profile')
ON DUPLICATE KEY UPDATE ID = LAST_INSERT_ID(ID), steamname = VALUES(steamname)
(OR)
INSERT INTO names (steamname)
VALUES ('$profile')
ON DUPLICATE KEY UPDATE steamname = VALUES(steamname)
Quoted from MySQL documentation
If a table contains an AUTO_INCREMENT column and INSERT ... UPDATE
inserts a row, the LAST_INSERT_ID() function returns the
AUTO_INCREMENT value. If the statement updates a row instead,
LAST_INSERT_ID() is not meaningful. However, you can work around this
by using LAST_INSERT_ID(expr).
You can't use INSERT ON DUPLICATE KEY UPDATE (IODKU) to update instead of inserting a new row unless you try to insert a value that conflicts with an existing primary or unique key column.
Since you're not specifying any value for ID in this INSERT, it will always increment the auto-increment primary key and insert a new row.
If you want the INSERT to replace the steamname for an existing row, you must specify the ID value in your INSERT.
Re your first comment:
I looked at your samples. It's not surprising that it created a new row. It generated a new auto-increment ID value because you didn't specify an ID in your INSERT. So it naturally created a new row. It has no way of telling which row you are trying to replace.
Here's a demo:
mysql> create table names (id serial primary key, steamname text);
mysql> insert into names (steamname) values ('Woogy')
on duplicate key update ID = LAST_INSERT_ID(ID), steamname = VALUES(steamname);
Ignore the ID = LAST_INSERT_ID(ID) part, this has no effect. It's a no-op. #Rahul suggested it, but unfortunately he or she is mistaken. I'm going to take out that term in subsequent tests.
So what happens in this INSERT? You specify a value for steamname, but no value for ID. So MySQL generates a new value for ID. That becomes the primary key value for a new row, and that row is inserted with the steamname 'Woogy'.
mysql> select * from names;
+----+-----------+
| id | steamname |
+----+-----------+
| 1 | Woogy |
+----+-----------+
Next we try to change the name to 'Chatyak':
mysql> insert into names (steamname) values ('Chatyak')
on duplicate key update steamname = VALUES(steamname);
Which row does this apply the change to? The INSERT does not specify an ID value, so MySQL auto-increments another new ID value. That value is the primary key for a new row, and that's the row that gets the steamname 'Chatyak'.
mysql> select * from names;
+----+-----------+
| id | steamname |
+----+-----------+
| 1 | Woogy |
| 2 | Chatyak |
+----+-----------+
What this means is that you can never trigger the ON DUPLICATE KEY part if you let the auto-increment generate a new ID value every time you INSERT. Every INSERT will result in a new row.
As I said above, ON DUPLICATE KEY does nothing unless you try to insert a value that conflicts with a primary or unique key for a row that already exists in the table. But in this case you aren't conflicting, you're always generating a new ID value. So it inserts a new row.
If you had a UNIQUE KEY constraint on steamname, then that would be another opportunity for triggering ON DUPLICATE KEY. But it still won't do what you want.
mysql> alter table names add unique key (steamname(20));
Then if you try to insert the same steamname as one that already exists, it will not insert a new row. It'll update the existing row.
mysql> insert into names (steamname) values ('Chatyak')
on duplicate key update ID = LAST_INSERT_ID(ID), steamname = VALUES(steamname);
Query OK, 0 rows affected (0.02 sec)
Note it says "0 rows" were inserted by that statement.
But this still doesn't allow you to change an existing steamname. If you specify a new name, it'll just insert a new row. If you specify the name that's already in use, it's a duplicate so it won't insert a new row, but it won't change the name either.
How do you expect the INSERT statement to apply to an existing row if you let it auto-increment a new ID value? Which existing row do you think it should conflict with?
Re your second comment:
You don't need a secondary unique key, I'm just trying to show you how IODKU works.
Before thinking about the SQL syntax, you have to think about the logic of the problem. In other words, if you specify a new name, which existing row do you want it to replace?
For example:
mysql> insert into names (id, steamname) values (123, 'Chatyak')
on duplicate key update steamname = VALUES(steamname);
This would work, because if a row exists with ID 123, this INSERT will cause a duplicate key conflict and therefore the ON DUPLICATE KEY clause will be triggered. But where did we get the value 123 in this example? Presumably in your application, you know what id you think you're updating. Is there a variable for that? Does it correspond to the user's session data?
**************************Edit:*************************
In response to the answer provided by #Rahul. This statement:
INSERT INTO names (steamname)
VALUES ('$profile')
ON DUPLICATE KEY UPDATE ID = LAST_INSERT_ID(ID), steamname = VALUES(steamname)
is technically valid but will always affect exactly 0 rows no matter what. Actually there are so many things wrong with it, that it is difficult to enumerate them all (which is the reason for this edit).
The usage of ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id) given in MySQL docs at the bottom of this page is an example of how to capture the id value of a row that has been updated i.e not inserted, since otherwise LAST_INSERT_ID() (with no argument) would return the last inserted id, which in the case of an update would not be meaningful in terms of the row in question.
So the above statement, if it ever worked — though it can't — would be effectively no different from this statement:
INSERT INTO names (steamname)
VALUES ('$profile')
ON DUPLICATE KEY UPDATE steamname = VALUES(steamname)
Except that you would also be updating that row's id value to the value it already was.
Aside from that, the reason the statement can never work is because the ON DUPLICATE KEY UPDATE clause will never be evaluated unless there is a duplicate key. And the same error that would occur without the ON DUPLICATE KEY UPDATE clause, will occur when we try to update steamname to equal the value of steamname i.e. the same value that tripped the ON DUPLICATE KEY UPDATE clause in the first place.
*****************************END OF EDIT***********************************
First: $profile is still an instance of SimpleXMLElement object when you try to use it in your query. So the SimpleXMLElement::__toString method is called, but SimpleXMLElement::__toString method only returns text content from directly inside the first element, and not text from any descendants. So if you have some element <steamname> and some element <steamID> nested within the parent element returned by your call to simplexml_load_file, their values will not be returned. See manual.
Second: when using the VALUES function with an UPDATE clause, the correct syntax is to refer to a column name/s like so DUPLICATE KEY UPDATE colname = VALUES(some_col_name). If you want to use an explicit value the syntax is DUPLICATE KEY UPDATE colname = 'value'. See manual.
Third: since you have id set to AUTO_INCREMENT, any rows inserted into the names table without explicitly inserting an id value or using a WHERE clause to specify an id value will create a new row with a new auto incremented id value, regardless of whether you use DUPLICATE KEY UPDATE or not because AUTO_INCREMENT will ensure that you never generate a duplicate key.
I think you may be able to make use of mysql's REPLACE statement instead. It's the simplest query that works for your described needs.
REPLACE INTO `names` (`id`,`steamname`)
VALUES ($id, $steamname)
where $id is $profile->steamID, and $steamname is the new(or not new) value for that row's steamname column.
The REPLACE statement will simply DELETE and then INSERT where an id value already exists. But since you're inserting the same id the effect is of updating the value of steamname in the row that contains a matching id value. Just be aware that if you have other columns besides id and steamname in the same table and you don't include their values in the REPLACE statement's VALUES then those values will be lost.
Fourth: for debuging these kinds of things its important to a) know what values you're actually passing into your query string. b) know that the basic query syntax you're using is correct, apart from any specific values you are(or aren't) using with it.
And finally: make things easy on yourself; since you're already using object notation with SimpleXMLElement to access element values, why not use it with mysqli also? Try this. It is UNTESTED, but it think it will work for you.
<?php
// Capture person's name and id from XML file
$profile = simplexml_load_file('UrlGoesHere.Com/?xml=1', 'SimpleXMLElement', LIBXML_NOCDATA);
$profile_id = (string)$profile->steamID;
$profile_name = (string)$profile->steamName;
//connect to database
$mysqli = new mysqli("localhost","username","password","databaseName");
// Check connection
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
//build query
//IMPROTANT: this will delete other fields(if any)
//> in the row, unless they are also refilled by this statement
$q = ("
REPLACE INTO `names` (`id`,`steamname`)
VALUES (?, ?)
");
//uncomment to debug query
//echo "<pre>$q</pre>";
//uncomment to debug values
//echo "<pre>profile_name: \n $profile_name</pre>";
//echo "<pre>profile_id: \n $profile_id</pre>";
//prepare statement using above query
$stmt = $mysqli->prepare($q);
//fill in '?'s with actual values
//first argument is the data types
//'i' [integer] 's' [string]
//follownig aruments fill in '?'s in order
$stmt->bind_param('is', $profle_id, $profile_name);
// execute statement
$stmt->execute();
//close statement
$stmt->close();
//close connection
$mysqli->close();
?>

Find highest value in column insert a new record 1 number higher in said column

I am trying to write a single MySQL insert query that will identify the highest value in a column and then increment it by one for the record being inserted. I thought when I made the table that I had this field set to auto_increment but it does not work for some reason. My current insert statement is:
INSERT INTO victoria (name, album, order_by) VALUES (:name, :album, :order)
The order_by field is the one that needs to increment by one.
If you want, for some reason, to increment a value of a column without using an auto_increment column you can do something like this
INSERT INTO victoria
SELECT :name, :album,
COALESCE((SELECT MAX(order_by) FROM victoria), 0) + 1;
Note: it might fail to provide you with a distinct value under heavy load, meaning several concurrent users inserting rows at the same time can grab the same MAX(order_by) value. Therefore if you are not planning to "reorder" rows latter you better stick with an auto_increment column.
Here is SQLFiddle demo

Avoid entering duplicate entries based on date, without using select statement

I am running a insert statement to insert data, but I want to check for any duplicate entries based on date and then do an entry.
All I want is if today a user enters product_name='x', 'x' is unique so that no one can enter product name x again today. But of course the next day they can.
I do not want to run a select before the insert to do the checking. Is there an alternative?
You can either use
1. Insert into... on duplicate update
2. insert.. ignore
This post will answer your question
"INSERT IGNORE" vs "INSERT ... ON DUPLICATE KEY UPDATE"
You can use the mysql insert into... on duplicate update syntax which will basically enter in a new row if one isn't there, or if the new row would have caused a key constraint to kick in, then it can be used to update instead.
Lets say you have the following table:
MyTable
ID | Name
1 | Fluffeh
2 | Bobby
3 | Tables
And ID is set as the primary key in the database (meaning it CANNOT have two rows with the same value in it) you would normally try to insert like this:
insert into myTable
values (1, 'Fluffster');
But this would generate an error as there is already a row with ID of 1 in it.
By using the insert on duplicate update the query now looks like this:
insert into myTable
values (1, 'Fluffster')
on duplicate key update Name='Fluffster';
Now, rather than returning an error, it updates the row with the new name instead.
Edit: You can add a unique index across two columns with the following syntax:
ALTER TABLE myTable
ADD UNIQUE INDEX (ID, `name`);
This will now let you use the syntax above to insert rows while having the same ID as other rows, but only if the name is different - or in your case, add the constraint on the varchar and date fields.
Lastly, please do add this sort of information into your question to start with, would have saved everyone a bit of time :)

How to find in PHP which part of the query "INSERT ... ON DUPLICATE KEY UPDATE..." succeeded?

I have MySQL table that doesn't have AUTO_INCREMENT field. In PHP I run this simple query:
mysql_query("INSERT INTO table ... ON DUPLICATE KEY UPDATE ....");
I need to be able to detect if a new row was inserted into table or if an old row was updated ? Is it possible somehow ?
mysql_affected_rows() returns 1 on an insert (because it inserted one row) and 2 on an update (because first it tried to insert, and then it updated, or something like that).
That is when you're just trying to add one row. You can have the ON DUPLICATE KEY UPDATE clause on multi-row inserts too, and then you'll get the sum of 1's and 2's for all the rows.

Categories