I've got my database set up with three tables - code, tags, and code_tags for tagging posts.
This will be the SQL query processed when a post is submitted. Each tag is sliced up by PHP and individually inserted using these queries.
INSERT IGNORE INTO tags (tag) VALUES ('$tags[1]');
SELECT tags.id FROM tags WHERE tag = '$tags[1]' ORDER BY id DESC LIMIT 1;
INSERT INTO code_tags (code_id, tag_id) VALUES ($codeid, WHAT_GOES_HERE?)
The WHAT_GOES_HERE? value at the end is what I need to know. It needs to be the ID of the tag that the second query fetched. How can I put that ID into the third query?
I hope I explained that correctly. I'll rephrase if necessary.
Edit: Thanks for your help so far but I'm still struggling a bit in regards to what was pointed out - if it's already there I can't get the inserted ID...?
If you use INSERT IGNORE and a new record is ignored (because of a unique key violation) mysql_insert_id() and LAST_INSERT_ID() don't have a meaningful value.
But you can use INSERT ... ON DUPLICATE KEY UPDATE and LAST_INSERT_ID(expr) to set the data you expect LAST_INSERT_ID() to return in case of a doublet.
Step-by-step:
Let's assume you have a table tags like
CREATE TABLE tags (
id int auto_increment,
tag varchar(32),
dummy int NOT NULL DEFAULT 0, /* for demo purposes only */
primary key(id),
unique key(tag)
)
Inserting a tag twice results in a duplicate key violation because of unique key(tag). That's probably the reason why you've used INSERT IGNORE. In that case MySQL ignores the violation but the new record is ignored as well. The problem is that you want the id of the record having tag='xyz' regardless of whether it has been newly created or it was already in the database. But right now mysql_insert_id()/LAST_INSERT_ID() can oly provide the id of a new record, not an ignored one.
With INSERT ...ON DUPLICATE you can react on such duplicate key violations. If the new record can be inserted (no violation) it behaves like a "normal" INSERT. But in case of a duplicate key violation the part after ON DUPLICATE KEY is executed like an UPDATE statement for the record with that particular index value already existing in the table. E.g. (with an empty table tags)
INSERT INTO tags (tag) VALUES ('tag A') ON DUPLICATE KEY UPDATE dummy=dummy+1
This will simply insert the record as if there was no ON DUPLICATE ... clause. id gets the next auto-increment value, dummy the default value of 0 and tag='tag A'. Let's assume the newly create auto_increment value was 1. The resulting record stored in MySQL is (id=1, tag='tag A', dummy=0) and LAST_INSERT_ID() will return 1 right after this query. So far so good.
Now if you insert the same record again with the same query a violation occurs because of the first record (id=1, 'tag=tag A', dummy=0). For this already exisitng record the UPDATE statement after ON DUPLICATE KEY is executed, i.e. the record becomes (id=1, tag='tag A', dummy=1). But since no new record has been created there was also no new auto_increment value and LAST_INSERT_ID() becomes meaningless. So still the same problem as with INSERT IGNORE.
But there is a "special" construct that allows you to set the value LAST_INSERT_ID() is supposed to return after the ON DUPLICATE KEY UPDATE statement has been executed.
id=LAST_INSERT_ID(id)
Looks strange but it really only sets the value LAST_INSERT_ID() will return.
If you use the statement
INSERT INTO
tags
(tag)
VALUES
('xyz')
ON DUPLICATE KEY UPDATE
id=LAST_INSERT_ID(id)
LAST_INSERT_ID() will always return the id of the record having tag='xyz' no matter if it was added by the INSERT part or "updated" by the ON DUPLICATE KEY part.
I.e. if your next query is
INSERT INTO
code_tags
(code_id, tag_id)
VALUES
(4711, LAST_INSERT_ID())
the tags.id for the tag 'xyz' is used.
The self-contained example script uses PDO and prepared statements. It should do more or less what you want to achieve.
$pdo = new PDO("mysql:host=localhost;dbname=test", 'localonly', 'localonly');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// set up temporary table and demo data
$pdo->exec('CREATE TEMPORARY TABLE tmpTags (id int auto_increment, tag varchar(32), primary key(id), unique key(tag))');
$pdo->exec('CREATE TEMPORARY TABLE tmpCode_tags (code_id int, tag_id int)');
$pdo->exec("INSERT INTO tmpTags (tag) VALUES ('tagA'), ('tagB')");
// prepare the statements
// set id=LAST_INSERT_ID(id), so LAST_INSERT_ID() gets a value even if the record is "ignored"
$stmtTags = $pdo->prepare('
INSERT INTO
tmpTags
(tag)
VALUES
(:tag)
ON DUPLICATE KEY UPDATE
id=LAST_INSERT_ID(id)
');
$stmtTags->bindParam(':tag', $tag);
$stmtCodeTags = $pdo->prepare('INSERT INTO tmpCode_tags (code_id, tag_id) VALUES (:codeid, LAST_INSERT_ID())');
$stmtCodeTags->bindParam(':codeid', $codeid);
// and some new records we want to insert
$testdata = array(
array('codeid'=>1, 'tags'=>'tagA tagC'), // tagA is already in the table "tags", tagC is a "new" tag
array('codeid'=>2, 'tags'=>'tagC tagD tagE') // tagC will already be inserted, tagD and atgE are "new"
);
// process (test)data
foreach($testdata as $data) {
// the parameter :codeid of $stmtCodeTags is bound to $codeid; assign it the "current" value
$codeid = $data['codeid'];
// split the tags
$tags = explode(' ', $data['tags']);
foreach($tags as $tag) {
// the parameter :tag is bound to $tag
// nothing more to do than to execute the statement
$stmtTags->execute();
// the parameter :codeid is bound to $codeid which was set to $codeid=$data['codeid']
// again nothing more to do than to execute the statement
$stmtCodeTags->execute();
}
}
unset($stmtTags);
unset($stmtCodeTags);
// let's see what we've got
$query = '
SELECT
ct.code_id, t.tag
FROM
tmpCode_tags as ct
JOIN
tmpTags as t
ON
ct.tag_id=t.id
';
foreach( $pdo->query($query, PDO::FETCH_NUM) as $row ) {
echo join(', ', $row), "\n";
}
prints
1, tagA
1, tagC
2, tagC
2, tagD
2, tagE
edit2: In case the PDO-part of the script and the prepared statements are intimidating, here's the same thing using the old php-mysql module. But I urge you to use parametrized prepared statements. Doesn't have to be PDO but I happen to like it. E.g. the mysqli module provides prepared statements as well, the old mysql module doesn't.
$mysql = mysql_connect('localhost', 'localonly', 'localonly') or die(mysql_error());
mysql_select_db('test', $mysql) or die(mysql_error());
// set up temporary table and demo data
mysql_query('CREATE TEMPORARY TABLE tmpTags (id int auto_increment, tag varchar(32), primary key(id), unique key(tag))', $mysql) or die(mysql_error());
mysql_query('CREATE TEMPORARY TABLE tmpCode_tags (code_id int, tag_id int)', $mysql) or die(mysql_error());
mysql_query("INSERT INTO tmpTags (tag) VALUES ('tagA'), ('tagB')", $mysql) or die(mysql_error());
// and some new records we want to insert
$testdata = array(
array('codeid'=>1, 'tags'=>'tagA tagC'), // tagA is already in the table "tags", tagC is a "new" tag
array('codeid'=>2, 'tags'=>'tagC tagD tagE') // tagC will already be inserted, tagD and atgE are "new"
);
// "prepare" the statements.
// This is nothing like the server-side prepared statements mysqli and pdo offer.
// we have to insert the parameters into the query string, i.e. the parameters must
// be escaped so that they cannot mess up the statement.
// see mysql_real_escape_string() for string literals within the sql statement.
$qsTags = "
INSERT INTO
tmpTags
(tag)
VALUES
('%s')
ON DUPLICATE KEY UPDATE
id=LAST_INSERT_ID(id)
";
$qsCodeTags = "
INSERT INTO
tmpCode_tags
(code_id, tag_id)
VALUES
('%s', LAST_INSERT_ID())
";
foreach($testdata as $data) {
// in this example codeid is a simple number
// let's treat it as a string literal in the statement anyway
$codeid = mysql_real_escape_string($data['codeid'], $mysql);
$tags = explode(' ', $data['tags']);
foreach($tags as $tag) {
// now $tag is certainly a string parameter
$tag = mysql_real_escape_string($tag, $mysql);
$query = sprintf($qsTags, $tag);
mysql_query($query, $mysql) or die(mysql_error());
$query = sprintf($qsCodeTags, $codeid);
mysql_query($query, $mysql) or die(mysql_error());
}
}
// let's see what we've got
$query = '
SELECT
ct.code_id, t.tag
FROM
tmpCode_tags as ct
JOIN
tmpTags as t
ON
ct.tag_id=t.id
';
$result = mysql_query($query, $mysql) or die(mysql_error());
while ( false!==($row=mysql_fetch_row($result)) ) {
echo join(', ', $row), "\n";
}
If I understand what you're attempting to achieve correctly, the second query is un-necessary - use mysql_insert_id to obtain the ID of previously inserted row, which is I presume what you need for "WHAT_GOES_HERE".
Related
I need to populate several columns of MYSQL table with data from arrays. So one column corresponds to one array. I have used the following code to fill just one column with data:
function addSystemDataTanks ($db, $tankNamesArray, $tankVolumesArray) {
$myArray = array();
$myString = implode ("'), ('",$myArray);
$statement = "replace into myTable (ID, NAME)";
$statement .= "values (' ";
$statement .= $myString;
$statement .= "')";
$result = mysqli_query($db, $statement);
if ($result) {
return true;
}
}
I need the ID field to be populated by auto-generated incremented numbers. But I need these rows to be replaced with new values next time this form is submitted. For the "replace" to work the ID has to be the same as previously used, otherwise it will just create new entries.
Also, is there a better way to input arrays as columns in MYSQL table, other than one by one, cause I need all row values to match to each other and the ID should be unique and start from 0 or 1 next time the form is submitted.
Thanks for any help.
If you just want the row ID to be incremental you're making life hard for yourself! When you create the table in MySQL, use AUTO_INCREMENT on the ID, then you can just enter a NULL value for the ID in your code:
CREATE TABLE blah (ID INT NOT NULL AUTO_INCREMENT, name VARCHAR(50), PRIMARY KEY(ID));
$sql = "INSERT INTO blah VALUES(NULL, 'Adam')";
"Adam" will now have an ID of 1 :)
I know this question but it didn't help.
I have a list with ids and values. Now I have to check if the id exists in my user database. If it does, then the row will be updated, otherwise I have to insert the id in another table (tmp_user).
Edit: Here is my try
IF NOT EXISTS (SELECT * FROM `wcf1_user` WHERE `steamID` = 1) THEN
INSERT INTO `wcf1_points_tmp` (`steamID`, `points`) VALUES (1, 2)
ELSE
// Update stuff......
END IF;
Result: #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 'IF NOT EXISTS (SELECT * FROM wcf1_user WHERE steamID = 1) THEN INSERT IN' at line 1
Thanks for your help. :)
If you specify ON DUPLICATE KEY UPDATE, and a row is inserted that would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an UPDATE of the old row is performed. For example, if column a is declared as UNIQUE and contains the value 1, the following two statements have identical effect:
INSERT INTO table (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE c=c+1;
UPDATE table SET c=c+1 WHERE a=1;
This is domain logic, which doesn't belong in the data tier. Do it in PHP instead:
<?php
// connect to the database
$DSN = "mysql:dbname=$dbname;charset=utf8";
$opt = array(PDO::MYSQL_ATTR_FOUND_ROWS => true);
$dbh = new PDO($DSN, $username, $password, $opt);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// one assumes you want to perform this operation atomically
$dbh->beginTransaction();
// attempt to update the row
$update = $dbh->prepare('
UPDATE wcf1_user
SET ......
WHERE steamID = ?
');
$update->execute(array($steamID));
// if the update didn't affect any rows
if (!$update->rowCount()) {
// insert into another table instead
$insert = $dbh->prepare('
INSERT INTO wcf1_points_tmp
(steamID, points)
VALUES
(?, ?)
');
$insert->execute(array($steamID, $points));
}
// tada!
$dbh->commit();
?>
I think you are missing semicolon at the end of insert statement, try following,
IF NOT EXISTS (SELECT * FROM `wcf1_user` WHERE `steamID` = 1) THEN
INSERT INTO `wcf1_points_tmp` (`steamID`, `points`) VALUES (1, 2);
ELSE
// Update stuff...... remember to add semicolon at the end
END IF;
Does it work for anyone? :P
I can properly get insert_id while inserting, but not on update. Of course contactsId column is AUTO_INCREMENT.
Whole code:
<?php
$mysqli = new mysqli('localhost', [USER], [PASSWORD], [DB]);
$mysqli->set_charset("utf8");
$query = 'INSERT INTO contacts (contactsName) VALUES ("Mariola")';
$result = $mysqli->query($query);
echo $mysqli->insert_id . '<br />';
$query = 'UPDATE contacts SET contactsName = "Mariola" WHERE contactsId = 289';
$result = $mysqli->query($query);
echo $mysqli->insert_id;
Output:
1514
0
I HAVE record with id 289, and update works fine.
This behavior is described very clear in the document.
mysqli::$insert_id -- mysqli_insert_id — Returns the auto generated
id used in the last query
If the last query wasn't an INSERT or UPDATE statement or if the
modified table does not have a column with the AUTO_INCREMENT
attribute, this function will return zero.
From MySQL documentation on LAST_INSERT_ID():
If expr is given as an argument to LAST_INSERT_ID(), the value of the argument is returned by the function and is remembered as the next value to be returned by LAST_INSERT_ID(). This can be used to simulate sequences:
Create a table to hold the sequence counter and initialize it:
mysql> CREATE TABLE sequence (id INT NOT NULL);
mysql> INSERT INTO sequence VALUES (0);
Use the table to generate sequence numbers like this:
mysql> UPDATE sequence SET id=LAST_INSERT_ID(id+1);
mysql> SELECT LAST_INSERT_ID();
The UPDATE statement increments the sequence counter and causes the next call to LAST_INSERT_ID() to return the updated value. The SELECT statement retrieves that value. The mysql_insert_id() C API function can also be used to get the value. See Section 20.6.7.37, “mysql_insert_id()”.
Maybe something like this will work:
$query = 'UPDATE contacts SET id = LAST_INSERT_ID(id), contactsName = "Mariola" WHERE contactsId = 289';
I've been stuck on this for a few hours now ...
Here's my code:
$SQLQuery1 = $db_info->prepare("SELECT COUNT(ID) FROM menusize WHERE typesize=:typesize");
$SQLQuery1->bindValue(':typesize',$_POST['typesize'],PDO::PARAM_STR);
$SQLQuery1->execute();
if($SQLQuery1->fetchColumn() > 0) {
$SQLQuery2 = $db_info->prepare("INSERT INTO menucatagorysize (menucatagory_ID,menusize_ID) VALUES (:catagoryid,(SELECT ID FROM menusize WHERE typesize=:typesize))");
$SQLQuery2->bindValue(':typesize',$_POST['typesize'],PDO::PARAM_STR);
$SQLQuery2->bindValue(':catagoryid',$_POST['catagoryid'],PDO::PARAM_STR);
$SQLQuery2->execute();
} else {
$SQLQuery2 = $db_info->prepare("INSERT INTO menusize (typesize) VALUES (:typesize);
SET #menusizeid=LAST_INSERT_ID();
INSERT INTO menucatagorysize (menusize_ID,menucatagory_ID) VALUES (#menusizeid,:catagoryid)");
$SQLQuery2->bindValue(':typesize',$_POST['typesize'],PDO::PARAM_STR);
$SQLQuery2->bindValue(':catagoryid',$_POST['catagoryid'],PDO::PARAM_STR);
$SQLQuery2->execute();
}
$SQLQuery3 = $db_info->prepare("SELECT DISTINCT(menuitem_ID) FROM menuprice WHERE menucatagory_ID=:catagoryid");
$SQLQuery3->bindValue(':catagoryid',$_POST['catagoryid'],PDO::PARAM_STR);
$SQLQuery3->execute();
$rows = $SQLQuery3->fetchAll(PDO::FETCH_ASSOC);
So, it will run through the if statement fine, running $SQLQuery1 and $SQLQuery2 (Which ever one is required) without any problems, errors or warnings. But, if it runs the else { part of the code, it will not run $SQLQuery3. Any thoughts?
Thanks :D
EDIT: Got it to work by doing $SQLQuery2=NULL in the else statement ... Sucks that I still cant figure out why it wouldnt work the original way.
It appears that you're trying to enforce a uniqueness constraint over the typesize column of your menusize table from within your application code. However, the database can do this for you—which will make your subsequent operations much simpler:
ALTER TABLE menusize ADD UNIQUE (typesize)
Now, one can simply attempt to insert the posted value into the table and the database will prevent duplicates arising. Furthermore, as documented under INSERT ... ON DUPLICATE KEY UPDATE Syntax:
If a table contains an AUTO_INCREMENT column and INSERT ... ON DUPLICATE KEY UPDATE inserts or updates a row, the LAST_INSERT_ID() function returns the AUTO_INCREMENT value. Exception: For updates, LAST_INSERT_ID() is not meaningful prior to MySQL 5.1.12. However, you can work around this by using LAST_INSERT_ID(expr). Suppose that id is the AUTO_INCREMENT column. To make LAST_INSERT_ID() meaningful for updates, insert rows as follows:
INSERT INTO table (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), c=3;
Therefore, you can do:
$db_info->prepare('
INSERT INTO menusize (typesize) VALUES (:typesize)
ON DUPLICATE KEY UPDATE typesize=LAST_INSERT_ID(typesize)
')->execute(array(
':typesize' => $_POST['typesize']
));
$db_info->prepare('
INSERT INTO menucatagorysize
(menusize_ID, menucatagory_ID)
VALUES
(LAST_INSERT_ID(), :catagoryid)
')->execute(array(
':catagoryid' => $_POST['catagoryid']
));
$stmt = $db_info->prepare('
SELECT DISTINCT menuitem_ID
FROM menuprice
WHERE menucatagory_ID = :catagoryid
');
$stmt->execute(array(
':catagoryid' => $_POST['catagoryid']
));
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// etc.
}
(As an aside, the English word is spelled cat*e*gory, not cat*a*gory.)
$insert = $dbh->prepare('INSERT INTO tags (tag_name) VALUES (:tag)');
$insert->bindParam(':tag', $tag, PDO::PARAM_STR);
foreach($tags as $tag) {
$insert->execute();
$tag_id = $dbh->lastInsertID();
echo $tag_id."+".$photo_id."<br />";
$sql = "INSERT INTO tagrefs (tag_id, photo_id) VALUES (:tag_id,:photo_id)";
$q = $dbh->prepare($sql);
$q->execute(array(':tag_id'=>$tag_id,
':photo_id'=>$photo_id));
}
This particular piece of code inserts tags related to uploaded photos into a table called 'tags'. It links the tag_id to the photo_id in a table called 'tagrefs'. This all works fine, until I use a tag twice. Which is logical, because nothing is inserted (tags are unique, I simply want the entry in 'tagrefs' to list the photo_id for my next photo with tag_id's that already exist)
How do I make it so that my code compares the tags the user put in and compares them, or that the values of existing tags are returned and put into 'tagrefs' properly? Thank you very much in advance for your time.
If you use INSERT ... ON DUPLICATE KEY UPDATE, then lastInsertID() will return the AUTO_INCREMENT field's value of a matched row even if an UPDATE is performed instead of an insertion.
To ensure that it also works in versions of MySQL prior to v5.1.12, one can explicitly set the insertion id with MySQL's LAST_INSERT_ID() function:
INSERT INTO tags
(tag_name)
VALUES
(:tag)
ON DUPLICATE KEY UPDATE
id = LAST_INSERT_ID(id)