mysql insert if not exists on joined tables - mysql

My tables look like this:
Table "posts"
posts.id = PK, auto-increment
posts.text = varchar
posts.tag_id 1 = int, FK to tags.id
posts.tag_id 2 = int, FK to tags.id
posts.tag_id 3 = int, FK to tags.id
Table "tags"
tags.id = PK, auto-increment
tags.tag = varchar
Now I want to insert the following data:
text: 'lorem ipsum'
tag1: 'music'
tag2: 'pop'
tag3: 'singer'
So I need a query that checks if tag1/tag2/tag3 already exist in "tags", inserts them otherwise and then insert those as foreign-keys + the "text" into a new row in "posts".
I had a look into mysql INSERT IF NOT EXISTS but I'm just stuck and don't know where to start. I know I could handle it with multiple queries but there has to be another, lighter way to achieve the same result.
Has anyone any experience with this?
Update
A friend of mine proposed something like this:
CREATE FUNCTION getTagID(tag VARCHAR('100')) RETURNS int
INSERT INTO posts (text,tag1,tag2,tag3)
VALUES ('lorem ipsum', getTagID('music'), getTagID('pop'), getTagID('singer'));
Of course the implementation of getTagId is still missing, but does that make sense? getTagID should select the id with the given tag, if it doesn't exist, insert it and return this. Any help is appreciated.
Solution
I created a custom function in MySql:
DELIMITER ;;
CREATE FUNCTION getTagID(tag VARCHAR(100)) RETURNS BIGINT
BEGIN
DECLARE tagID BIGINT;
SET tagID = (SELECT id FROM tags WHERE text = tag);
IF tagID IS NULL
THEN
INSERT INTO tags (text) VALUES (tag);
SET tagID = (SELECT LAST_INSERT_ID());
END IF;
RETURN tagID;
END;;
DELIMITER ;
and now I can just insert into posts like that:
INSERT INTO posts (text,tag1,tag2,tag3)
VALUES ('lorem ipsum', getTagID('music'), getTagID('pop'), getTagID('singer'));
with that function, which inserts into "tags" only if the tag does not yet exist and gives back the ID of the existing or newly created tag. Yipeee :)

You have to insert to posts, then insert to tags. There is no multi-table insert solution in SQL.
You can't even insert the post text to posts from a trigger on tags, because the insert to tags can only carry columns that belong to tags.
You can use INSERT IGNORE or REPLACE or INSERT ... ON DUPLICATE KEY UPDATE if you need to avoid duplicates in the tags table. See my answer to "INSERT IGNORE" vs "INSERT ... ON DUPLICATE KEY UPDATE" for more details on that.
Re your comment.
You can get the generated PK only if you let the auto-increment mechanism generate a new id. If you let auto-increment happen, you're guaranteed not to result in a duplicate PK.
If you specify an id and bypass auto-increment, you can't get the id anyway. But you don't need to query for the value if you specified it in your INSERT -- you should have it already.
The only other case is if you have a secondary unique key that's not auto-increment.
CREATE TABLE foo (id int auto_increment primary key, name varchar(10), unique key (name));
INSERT INTO foo (name) VALUES ('bill');
SELECT LAST_INSERT_ID(); -- returns 1
INSERT INTO foo (name) VALUES ('bill');
SELECT LAST_INSERT_ID(); -- returns 1
This is slightly confusing because the last insert id comes from the last INSERT that succeeded and generated an id.
Re your update with INSERT INTO posts (text,tag1,tag2,tag3). That's called repeating groups. What if you need four tags? What if a post has only two tags? How do you make an indexed search for posts with a given tag?

Related

Insert foreign key into table

I have two tables.
basically i want to insert an id and a string into a table
However, id is a foreign key to another table in which customerId is the primary key
Furthermore my parent table has name
What i have, is name and the stringthat i get from a web ui. However, since i dont have the id that match the customerid of name in the parent table, i don't know how to insert it.
i got this so far, which by the way is my silly attempt to work my human logic around this issue:
INSERT INTO `PostDb`(`Offer`)
VALUES ("String") AND PostDb.id
WHERE CustomerDb.id = PostDb.id AND CustomerDb.name = "MyNameThatIHave"
What would work though. is that i do the following:
SELECT PostDb.id
FROM `PostDb` JOIN CustomerDb
WHERE `CustomerId` = CustomerDb.id AND CustomerDb.name = "MyNameThatIHave"
And then use the id that i get in a new insert command like this:
INSERT INTO `PostDb`(`CustomerId`, `Offer`)
VALUES ("THE ID I GOT BEFORE","STRING")
Basically i want to achieve in ONE query, what the two before stated queries does
You can use SELECT to get values for insert:
INSERT INTO `PostDb`(`Offer`, customerid)
SELECT 'Whatever', id FROM customerdb
WHERE name = 'MyNameThatIHave'
Have you tried LAST_INSERT_ID() function which gives you the last inserted ID PK provided that ID is an auto_increment column.
Once you get that, then you can insert in your child table in your FK column along with the rest attributes.
In that case, use a INSERT INTO .. SELECT FROM construct like
INSERT INTO `PostDb`(`CustomerId`, `Offer`)
SELECT PostDb.`CustomerId`, 'Some Value'
FROM `PostDb` JOIN CustomerDb
ON `PostDb`.`CustomerId` = CustomerDb.id
WHERE CustomerDb.name = "MyNameThatIHave";

INSERT INTO if NOT EXISTS or increment if EXISTS

My question is. I have the table 'popular' with fields 'id', 'title', 'popularity' in MySQL. I need insert info into field "title", if info not exists or increment value of 'popularity', if info exists. What is the best practice to do it?
INSERT ... ON DUPLICATE KEY UPDATE Syntax
INSERT INTO popular (title, popularity) VALUES (:the_title, 1)
ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id), popularity = popularity + 1
Make sure you have an unique constraint on title
id = LAST_INSERT_ID(id) allows you to get the id of the record you inserted/updated using LAST_INSERT_ID (or the equivalent function for your MySQL API). If you don't need the id you can remove it from the UPDATE list.
sample code:
if exists (select * from contact where name = #name) then
select -1;
else
insert into contact(name) values(#name);
select last_insert_id();
end if;
reference:
http://ask.sqlservercentral.com/questions/88038/best-mysql-practice-to-insert-a-record-if-it-does.html
http://bugs.mysql.com/bug.php?id=19243
http://mikefenwick.com/blog/insert-into-database-or-return-id-of-duplicate-row-in-mysql/

Insert a record only if it is not present

I need to create a query to insert some records, the record must be unique. If it exists I need the recorded ID else if it doesnt exist I want insert it and get the new ID. I wrote that query but it doesnt work.
SELECT id FROM tags WHERE slug = 'category_x'
WHERE NO EXISTS (INSERT INTO tags('name', 'slug') VALUES('Category X','category_x'));
It's called UPSERT (i.e. UPdate or inSERT).
INSERT INTO tags
('name', 'slug')
VALUES('Category X','category_x')
ON DUPLICATE KEY UPDATE
'slug' = 'category_x'
MySql Reference: 13.2.5.3. INSERT ... ON DUPLICATE KEY UPDATE Syntax
Try something like...
IF (NOT EXISTS (SELECT id FROM tags WHERE slug = 'category_x'))
BEGIN
INSERT INTO tags('name', 'slug') VALUES('Category X','category_x');
END
ELSE
BEGIN
SELECT id FROM tags WHERE slug = 'category_x'
END
But you can leave the ELSE part and SELECT the id, this way the query will always return the id, irrespective of the insert...
MySQL has nice REPLACE. It is easy to use and remember it's syntax as same as INSERT.
in you case, just run following query.
REPLACE INTO tags('name', 'slug') VALUES('Category X','category_x')
It acts like INSERT when no unique constraint violation. If duplicated value found on PK or UNIQUE key, then other columns will be UPDATED with given values. It is done by DELETE duplicated record and INSERT new record.

Insert and select the id of a unique element in one query

I have a simple table like this
CREATE TABLE authid(
id INT NOT NULL AUTO_INCREMENT,
authid VARCHAR(128) NOT NULL UNIQUE,
PRIMARY KEY(id)
);
Now if I insert a value with
INSERT INTO authid(authid) VALUES('test');
It will work fine and return the inserted id the first time, but if I do it again when the authid already exists (notice that we have authid marked as UNIQUE) it will return an error.
Is there a way achieve this this in one SQL statement: Insert it, get the id and if it already exists, still get the id.
Take a look at this: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
If you're using MySQL 5.0 or higher you can use the "INSERT ... ON DUPLICATE KEY UPDATE" syntax. You may be able to combine that with LAST_INSERT_ID() (I'm not positive about that)
So:
insert into authid (authid) values ('test') on duplicate key update id=LAST_INSERT_ID(id), authid='test';
select LAST_INSERT_ID();
Well indeed if you try to insert 2 times the same value in a UNIQUE field, it won't work, that's the point of UNIQUE fields.
If I understand well, you want to know if it's possible whether to use an INSERT or an UPDATE statement depending on the existance of an item or not ? Then you need 2 queries, 1 to test existence, the other to insert new value or update existing one
Insert the value conditionally (i.e. if it doesn't exist). Whether the insert takes place or not, by the end of the statement the result will be the same: the value will be in the table. So, just select the ID of the row that matches that value. Or, speaking in SQL, like this:
INSERT INTO authid (authid)
SELECT 'test'
WHERE NOT EXISTS (
SELECT *
FROM authid
WHERE authid = 'test'
);
SELECT id
FROM authid
WHERE authid = 'test'
;

MySQL Insert into multiple tables? (Database normalization?) [duplicate]

This question already has answers here:
Insert into multiple tables in one query
(6 answers)
Closed 1 year ago.
I tried searching a way to insert information in multiple tables in the same query, but found out it's impossible?
So I want to insert it by simply using multiple queries i.e;
INSERT INTO users (username, password) VALUES('test', 'test')
INSERT INTO profiles (userid, bio, homepage) VALUES('[id of the user here?]','Hello world!', 'http://www.stackoverflow.com')
But how can I give the auto-increment id from the users to the "manual" userid for the profile table?
No, you can't insert into multiple tables in one MySQL command. You can however use transactions.
BEGIN;
INSERT INTO users (username, password)
VALUES('test', 'test');
INSERT INTO profiles (userid, bio, homepage)
VALUES(LAST_INSERT_ID(),'Hello world!', 'http://www.stackoverflow.com');
COMMIT;
Have a look at LAST_INSERT_ID() to reuse autoincrement values.
You said "After all this time trying to figure it out, it still doesn't work. Can't I simply put the just generated ID in a $var and put that $var in all the MySQL commands?"
Let me elaborate: there are 3 possible ways here:
In the code you see above. This
does it all in MySQL, and the
LAST_INSERT_ID() in the second
statement will automatically be the
value of the autoincrement-column
that was inserted in the first
statement.
Unfortunately, when the second statement itself inserts rows in a table with an auto-increment column, the LAST_INSERT_ID() will be updated to that of table 2, and not table 1. If you still need that of table 1 afterwards, we will have to store it in a variable. This leads us to ways 2 and 3:
Will stock the LAST_INSERT_ID() in
a MySQL variable:
INSERT ...
SELECT LAST_INSERT_ID() INTO #mysql_variable_here;
INSERT INTO table2 (#mysql_variable_here, ...);
INSERT INTO table3 (#mysql_variable_here, ...);
Will stock the LAST_INSERT_ID() in a
php variable (or any language that
can connect to a database, of your
choice):
INSERT ...
Use your language to retrieve the LAST_INSERT_ID(), either by executing that literal statement in MySQL, or using for example php's mysql_insert_id() which does that for you
INSERT [use your php variable here]
WARNING
Whatever way of solving this you choose, you must decide what should happen should the execution be interrupted between queries (for example, your database-server crashes). If you can live with "some have finished, others not", don't read on.
If however, you decide "either all queries finish, or none finish - I do not want rows in some tables but no matching rows in others, I always want my database tables to be consistent", you need to wrap all statements in a transaction. That's why I used the BEGIN and COMMIT here.
fairly simple if you use stored procedures:
call insert_user_and_profile('f00','http://www.f00.com');
full script:
drop table if exists users;
create table users
(
user_id int unsigned not null auto_increment primary key,
username varchar(32) unique not null
)
engine=innodb;
drop table if exists user_profile;
create table user_profile
(
profile_id int unsigned not null auto_increment primary key,
user_id int unsigned not null,
homepage varchar(255) not null,
key (user_id)
)
engine=innodb;
drop procedure if exists insert_user_and_profile;
delimiter #
create procedure insert_user_and_profile
(
in p_username varchar(32),
in p_homepage varchar(255)
)
begin
declare v_user_id int unsigned default 0;
insert into users (username) values (p_username);
set v_user_id = last_insert_id(); -- save the newly created user_id
insert into user_profile (user_id, homepage) values (v_user_id, p_homepage);
end#
delimiter ;
call insert_user_and_profile('f00','http://www.f00.com');
select * from users;
select * from user_profile;
What would happen, if you want to create many such records ones (to register 10 users, not just one)?
I find the following solution (just 5 queryes):
Step I: Create temporary table to store new data.
CREATE TEMPORARY TABLE tmp (id bigint(20) NOT NULL, ...)...;
Next, fill this table with values.
INSERT INTO tmp (username, password, bio, homepage) VALUES $ALL_VAL
Here, instead of $ALL_VAL you place list of values: ('test1','test1','bio1','home1'),...,('testn','testn','bion','homen')
Step II: Send data to 'user' table.
INSERT IGNORE INTO users (username, password)
SELECT username, password FROM tmp;
Here, "IGNORE" can be used, if you allow some users already to be inside. Optionaly you can use UPDATE similar to step III, before this step, to find whom users are already inside (and mark them in tmp table). Here we suppouse, that username is declared as PRIMARY in users table.
Step III: Apply update to read all users id from users to tmp table. THIS IS ESSENTIAL STEP.
UPDATE tmp JOIN users ON tmp.username=users.username SET tmp.id=users.id
Step IV: Create another table, useing read id for users
INSERT INTO profiles (userid, bio, homepage)
SELECT id, bio, homepage FROM tmp
have a look at mysql_insert_id()
here the documentation: http://in.php.net/manual/en/function.mysql-insert-id.php
try this
$sql= " INSERT INTO users (username, password) VALUES('test', 'test') ";
mysql_query($sql);
$user_id= mysql_insert_id();
if(!empty($user_id) {
$sql=INSERT INTO profiles (userid, bio, homepage) VALUES($user_id,'Hello world!', 'http://www.stackoverflow.com');
/* or
$sql=INSERT INTO profiles (userid, bio, homepage) VALUES(LAST_INSERT_ID(),'Hello world!', 'http://www.stackoverflow.com'); */
mysql_query($sql);
};
References
PHP
MYSQL
Just a remark about your saying
Hi, I tried searching a way to insert information in multiple tables in the same query
Do you eat all your lunch dishes mixed with drinks in the same bowl?
I suppose - no.
Same here.
There are things we do separately.
2 insert queries are 2 insert queries. It's all right. Nothing wrong with it. No need to mash it in one.
Same for select. A query must be sensible and do its job. That's the only reasons. Number of queries is not.
There is no point in looking for a way to stuff different queries in one call. Different calls is how the database API is meant to work.
For PDO You may do this
$dbh->beginTransaction();
$stmt1 = "INSERT INTO users (username, password) VALUES('test', 'test')";
$stmt2 = "INSERT INTO profiles (userid, bio, homepage) VALUES('LAST_INSERT_ID(),'Hello world!', 'http://www.stackoverflow.com')";
$sth1 = $dbh->prepare($stmt1);
$sth2 = $dbh->prepare($stmt2);
$sth1->execute (array ('test','test'));
$sth2->execute (array ('Hello world!','http://www.stackoverflow.com'));
$dbh->commit();