I have the following problem:
Every author has a name and n-aliases for this name. Every alias comes from a source. Every alias has m sources. For example
AUTHOR| ALIAS | SOURCE
-------------------------
Will| Willy |George
Will| Bill | Jenny
William| Will| Francis
William| Bill| Maya
I have one table for the author and his name, one for all of his aliases:
CREATE TABLE alias (
authors_id INT NOT NULL,
alias VARCHAR(150) NOT NULL,
id INT NOT NULL AUTO_INCREMENT
PRIMARY KEY (author_id,alias);
id serves as foreign key.
Here's the second table for the sources
CREATE TABLE alias_source (
id INT NOT NULL AUTO_INCREMENT
source VARCHAR(150) NOT NULL,
alias_id INT NOT NULL,
PRIMARY KEY (id)
FOREIGN KEY (alias_id) REFERENCES alias(id);
Now I need an MySQL insert statement for when I insert
author,alias,source into alias that the source is inserted into alias_source.
And on duplicate alias no only a new source is added.
An INSERT statement in SQL can only insert into one table, and can only list columns of that one table. You can't include your source column in the INSERT statement, because it's not a column of the alias table.
Triggers can allow you to insert into a secondary table, but the trigger needs to know the value to insert. In this case the trigger doesn't have any way of getting the value for source.
This is a task that is much easier to do with two INSERT statements.
INSERT IGNORE INTO alias SET author_id = ?, alias = ?;
INSERT INTO alias_source ...
But you have a problem because your alias table has an auto-increment column id, but this column is not part of a key. In InnoDB, you must make the auto-increment column the first column of a key.
Your alias_source table has a foreign key referencing alias(id) but that's not allowed. A foreign key must reference a key of the parent table. It should reference the unique or primary key, and should reference all columns of the key (otherwise you get a row in a child table that might reference multiple rows in the parent table, and that makes no sense).
If you want to use an auto-increment column for your alias table, make it the primary key, and put a secondary UNIQUE constraint on the other columns.
CREATE TABLE alias (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
authors_id INT NOT NULL,
alias VARCHAR(150) NOT NULL,
UNIQUE KEY (author_id,alias));
Then query the id after you insert. Whether you inserted a new row, or the row already existed, you'll get the id for the row with the chosen author and alias.
INSERT IGNORE INTO alias SET author_id = ?, alias = ?;
SELECT id FROM alias WHERE author_id = ? AND alias = ? INTO #id;
INSERT INTO alias_source SET alias_id = #id, source = ?;
Re your followup question in comment:
Good idea, but it doesn't work the way you may think it does. You can do a dummy set of id=id and set a session variable as a side effect.
mysql> insert into alias set author_id=1, alias='alias'
on duplicate key update id = #id:=id;
mysql> select #id;
+------+
| #id |
+------+
| NULL |
+------+
But why didn't it set the session variable to the id? Because this insert was not a duplicate, it was a new row. So the id value had not yet been generated by auto-increment at the time the IODKU executed.
If you subsequently do the same IODKU for a row that is a duplicate, the id value has previously been added to the row, so you get the value in the side-effect:
mysql> insert into alias set author_id=1, alias='alias'
on duplicate key update id = #id:=id;
mysql> select #id;
+------+
| #id |
+------+
| 1 |
+------+
So you'd have to write application code anyway to check if #id is NULL or not, and do a SELECT id query if #id is NULL.
That seems more complicated than just doing the SELECT id as a standard action following the INSERT IGNORE like I showed above.
Related
What I have is a table of completed training. Each user has a username. Each user may completed numerous courses.
The table has the following headers:
+-------------------------+----------+---------+---------+---------+---------+-----------+
| recordnumber (KEY - AI) | username | type | course | status | started | completed |
+-------------------------+----------+---------+---------+---------+---------+-----------+
| int | varchar | varchar | varchar | varchar | date | date |
+-------------------------+----------+---------+---------+---------+---------+-----------+
And I have a PHP script set up to populate the db from a CSV upload.
What I'm trying to achieve is for it to add new rows, and to update existing ones.
The problem is that recordnumber (they key, unique field) is not constant. So instead of doing a "ON DUPLICATE KEY" query, I want to do it based on whether username and course already exist as a row.
Basically to say "If this username already has this course, update the other fields. If the username does not have this course, add this as a new row".
The query that I have at the moment (which works based on key) is:
INSERT into table(recordnumber, username,type,course,status,started,completed) values('$data[0]','$data[1]','$data[2]','$data[3]','$data[4]','$data[5]','$data[6]')
ON DUPLICATE KEY UPDATE username='$data[1]',type='$data[2]',course='$data[3]',status='$data[4]',started='$data[5]',completed='$data[6]'
Any thoughts on how I could amend the query to get it to check based on username and course instead of duplicate key?
Thank you. :-)
The most correct way would be to create a unique index on username - course columns and use on duplicate key update.
Obviously, you can issue a select before the insert checking for existing record with same user name and course and issue an insert or an update as appropriate.
create a key on the username and course column and then use on duplicate key
CREATE TABLE test (
username varchar(255) NOT NULL,
course varchar(255),
num_entries INT DEFAULT 0,
UNIQUE KEY (username, course)
);
insert into test (username, course) values
('billybob', 'math'),
('billy', 'math'),
('billybob', 'math'),
('bob', 'math')
ON DUPLICATE KEY UPDATE num_entries = num_entries + 1;
this is a simple example, but you should understand what to do from here
SAMPLE FIDDLE
so putting this to work on your table
ALTER TABLE `courses` -- assuming the table is named courses
ADD CONSTRAINT `UK_COURSE_USERNAME` UNIQUE (username, course);
then your insert should just be the same as what you have
Example query in reference to my comment above.
IF EXISTS(SELECT id FROM Table WHERE username = '$data[1]' AND course <> '$data[3]')
(
UPDATE username='$data[1]',type='$data[2]',course='$data[3]',status='$data[4]',started='$data[5]',completed='$data[6]'
)
(
INSERT into table(recordnumber, username,type,course,status,started,completed) values('$data[0]','$data[1]','$data[2]','$data[3]','$data[4]','$data[5]','$data[6]')
)
You might use ON DUPLICATE KEY UPDATE if you added unique constraint for username and course value pair like this:
ALTER TABLE `table` ADD CONSTRAINT `UK_table_username_course` UNIQUE (username, course);
I have a table created by this SQL statement:
CREATE TABLE employees (
id INTEGER NOT NULL AUTO_INCREMENT,
name VARCHAR(20),
PRIMARY KEY (id)
);
I would like to insert into the table using something like
INSERT IGNORE INTO employees (name) values ('foo');
but for that statement to do nothing if there is already a person with a name 'foo' in the table. Is there a statement out there that ignores duplicates on a field other than a primary key or a field that is defined as unique?
INSERT INTO employees (name)
SELECT "foo" name FROM (select count(*) c
from employees
where name = "foo"
having c = 0) x;
You should have an index on name for efficiency. I'm not sure why you don't want to make it a unique index.
FIDDLE
I have a table user. It has columns id and email.
USER TABLE
id | email
1 | xxx#gmail.com
2 | yyy#gmail.com
The id is a PRIMARY KEY AUTO_INCREMENT and the email is an UNIQUE KEY.
When I insert a new row in the table and there is a DUPLICATE KEY exception thrown. I want to fetch the id on which the DUPLICATE KEY exception was thrown.
Right now I am doing this -
BEGIN
DECLARE EXIT HANDLER FOR 1062
BEGIN
SELECT id
INTO id
FROM user
WHERE email = 'xxx#gmail.com';
END;
INSERT INTO user
(email)
VALUES
('xxx#gmail.com');
SELECT LAST_INSERT_ID() INTO id;
END;
I want to know if there is a better way to do this. That is to avoid scanning the table again to get the id for which it had already scanned to check the uniqueness of the email.
In scaning by UNIQUE KEY BTREE is used so it's quite fast.
Don't you want check for existing of value by yourself in additional select query
Use INSERT ... ON DUPLICATE KEY UPDATE, then get the autoincremented id as usual:
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;
This question here:
MySQL: INSERT IGNORE or ON DUPLICATE KEY UPDATE with checking multiple and not unique columns
Is similar, but not quite.
I am trying to build a watch list, using a table as follows:
recordId | itemId | userId | dateAdded
The recordId is the only unique column and ss an autoincrement ID field.
itemId, userId and dateAdded can be duplicated individually, but not altogether.
So, if a user has already added an item to his/her watch list, I want it to just update the dateAdded.
Rows therefore may contain the same userId many times (as the user may wish to 'watch' multiple items) and it may include the itemId many times (as the same item may be 'watched' by many users) but a combination of both the userId and itemId matching a record which already exists should cause an update.
Is there a single statement way to do this, or do I need to ask the database if userId = & postingUser & and itemId = & requestedId & before attempting an insert?
Here's how you can do this:
insert into <tablename> (itemId, userId)
values (?, ?)
on duplicate key update dateAdded = now();
where the ?'s stand for the new values you're inserting
This is assuming you have defined a unique index for (itemId, userId):
CREATE TABLE <table> (
`recordId` int(11) NOT NULL AUTO_INCREMENT,
`itemId` int(11) DEFAULT NULL,
`userId` int(11) DEFAULT NULL,
`dateAdded` timestamp,
PRIMARY KEY (`recordId`),
UNIQUE KEY itemuser (`itemId`,`userId`)
) ENGINE=InnoDB;
How this works:
when you try to insert a row, MySQL will check for key conflicts
since you've created a unique key for the combination of (itemId, userId), this will be checked
if there is no conflict, MySQL will go ahead and insert the row
if there is a conflict, MySQL will fall back to the on duplicate key clause, which updates the dateAdded column's value
I have a table that has a column which holds the id of a row in another table. However, when table A is being populated, table B may or may not have a row ready for table A.
My question is, is it possible to have mysql prevent an invalid value from being entered but be ok with a NULL? or does a foreign key necessitate a valid related value?
So... what I'm looking for (in pseudo code) is this:
Table "person" id | name
Table "people" id | group_name | person_id (foreign key id from table person)
insert into person (1, 'joe');
insert into people (1, 'foo', 1)//kosher
insert into people (1, 'foo', NULL)//also kosher
insert into people(1, 'foo', 7)// should fail since there is no id 7 in the person table.
The reason I need this is that I'm having a chicken and egg issue where it makes perfect sense for the rows in the people table to be created before hand (in this example, I'm creating the groups and would like them to pre-exist the people who join them). And I realize that THIS example is silly and I would just put the group id in the person table rather than vice-versa, but in my real-world problem that is not workable.
Just curious if I need to allow any and all values in order to make this work, or if there's some way to allow for null.
If you set the column as nullable then it can contain NULL even if it is a foreign key referencing a column in another table.
Foreign keys can be null.
When the row in the referenced table is entered you'll have to UPDATE that row to point to it.
You set a foreign key column to accept nulls by setting the optionality of the column to NULL:
DROP TABLE IF EXISTS `example`.`tableb`;
CREATE TABLE `example`.`tableb` (
`id` int(10) unsigned NOT NULL auto_increment,
`person_id` int(10) unsigned default NULL, -- notice, !say "NOT NULL" like id
PRIMARY KEY (`id`),
CONSTRAINT `FK_tableb_1` FOREIGN KEY (`person_id`) REFERENCES `tablea` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
That said, tablea has to have at least one record in it before you attempt to insert a null value into the tableb reference column. Otherwise, MySQL will throw an error (for me anyways, on 4.1).