I have two dependent tables as
CREATE TABLE posts
(
post_id int(11) unsigned NOT NULL AUTO_INCREMENT,
title varchar(255),
PRIMARY KEY(post_id)
) ENGINE=InnoDB
CREATE TABLE post_meta
(
post_id int(11) unsigned REFERENCES posts(post_id) ON DELETE CASCADE,
info varchar(255),
PRIMARY KEY(post_id)
) ENGINE=InnoDB
Question 1: After INSERTing into posts, post_meta does not accept value with error Duplicate entry XX for key 'PRIMARY. How should I modify the table structure?
Question 2: How can I set to create a corresponding row in post_meta upon INSERT INTO posts? I mean creating an empty row (only having id of the FK) in post_meta when creating a row in posts. In other words having the same number of rows in two columns without any INSERT into the second column.
Your current implementation looks as out of normalized form. Are you sure you need to keep the data separated to two different tables? Maybe
CREATE TABLE posts
(
post_id int(11) unsigned NOT NULL AUTO_INCREMENT,
title varchar(255),
info varchar(255),
PRIMARY KEY(post_id)
) ENGINE=InnoDB
will do?
Speculating: if you're doing it that way because of security issues then MySQL supports Column-level privileges.
If normalizing your data is unacceptable for some reasons then you can just make post_id primary key in both tables (don't make it foreign key!) and add INSERT and DELETE triggers on table posts which will insert or delete corresponding rows from post_meta.
UPDATE
You said in comment that you can have multiple records in post_meta table corresponding to single record in posts. That changes a lot:
You MUST NOT use primary key on post_id in table post_meta. Primary key MUST be unique in table scope.
You can set up your foreign key (the one you already tried to) - this will ensure that metadata is deleted automatically with posts.
If you do need PRIMARY KEY defined on post_meta then you shoud add a new (possibly auto_increment field) to post_meta table and use it as primary key. (Also, table can exist even without primary key but it's against most DB guidelines)
If you need to create a meta record(s) automatically for each post then you can add an INSERT trigger on posts as I've already suggested. Another approach is using a stored procedure (and only stored procedure) for adding records to posts - and in this SP you can write some SQL to insert necessary records to post_meta.
Related
I have a main table called results. E.g.
CREATE TABLE results (
r_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
r_date DATE NOT NULL,
system_id INT NOT NULL,
FOREIGN KEY (system_id) REFERENCES systems(s_id) ON UPDATE CASCADE ON DELETE CASCADE
);
The systems table as:
CREATE TABLE systems (
s_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
system_name VARCHAR(50) NOT NULL UNIQUE
);
I'm writing a program in Python with MySQL connector. Is there a way to add data to the systems table and then auto assign the generated s_id to the results table?
I know I could INSERT into systems, then do another call to that table to see what the ID is for the s_name, to add to the results table but I thought there might be quirk in SQL that I'm not aware of to make life easier with less calls to the DB?
You could do what you describe in a trigger like this:
CREATE TRIGGER t AFTER INSERT ON systems
FOR EACH ROW
INSERT INTO results SET r_date = NOW(), system_id = NEW.s_id;
This is possible only because the columns of your results table are easy to fill in from the data the trigger has access to. The auto-increment fills itself in, and no additional columns need to be filled in. If you had more columns in the results table, this would be harder.
You should read more about triggers:
https://dev.mysql.com/doc/refman/8.0/en/create-trigger.html
https://dev.mysql.com/doc/refman/8.0/en/triggers.html
Here is my problem :
I want to create relations between two items of the same type.
Basically, I have a 'tag' table and I want to establish relations between tags.
I created a table 'tag_relations'. In this table, each line would represent one relation. It will be composed with 2 atributes : tag_id (which is the id of the tag concerned by the relation), and relation (which is the id of the tag related to the first tag represented by tag_id).
I set the primary key of my 'tag_relations' table as the couple of these 2 attributes.
Here is how I created my table :
CREATE TABLE `tag_relation` (
`tag_id` int(11) NOT NULL,
`relation` int(11) NOT NULL,
PRIMARY KEY (`tag_id`,`relation`),
KEY `relation` (`relation`),
CONSTRAINT `tag_relation_ibfk_1` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`),
CONSTRAINT `tag_relation_ibfk_2` FOREIGN KEY (`relation`) REFERENCES `tag` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
My question is : how can I make sure, if I already have a couple (1,2) in my table (so tag 1 is related to tag 2), that it's impossible for me to insert the couple (2,1) (because this relation would already exist implicitly).
Am I forced to create a trigger ?
Thanks in advance
Some databases support indexes on expressions. Since 5.7, MySQL has generated columns with indexes. This allows you to do:
alter table tag_relation add tag1 generated always as (least(tag1, relation));
alter table tag_relation add tag2 generated always as (greatest(tag1, relation));
create unique index unq_tag_relation_tag1_tag2 on tag_relation(tag1, tag2);
In earlier versions, you would need an insert (and possibly update) trigger to ensure data integrity.
There are two tables - posts and comments:
create table posts
(
id integer not null primary key auto_increment,
body text not null
);
create table comments
(
id integer not null primary key auto_increment,
body text not null,
post_id integer not null references posts(id)
);
Now I want to create one more table - reports("bad post" flags) and I want it to store reports for both posts and comments.
create table reports
(
id integer not null primary key auto_increment,
obj_type tinyint not null, /* '1' for posts and '2' for comments */
obj_id integer not null,
body text not null
);
alter table reports add foreign key(obj_id) references posts(id) on delete cascade;
alter table reports add foreign key(obj_id) references comments(id) on delete cascade;
As you see there are two references in a single field (I differentiate them by obj_id), and the question is - Is it all right to do like this ?
If not what would be better solution?
Thanks in advance.
Intuitively that feels not the right way to do it. I think MySQL would be confused too; how would it validate that a constraint is being met; would it try posts first, or comments first ... maybe both?
Personally I would choose to create two link tables:
comments <-> reports
posts <-> reports
That way you disambiguate the obj_id properly.
you just need to reference your comments table since it already references the posts table, this way whenever you get a report you have the key to the comment and the key to the post.
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).
I have a simple table set up with two columns, each column is a key value. the values stored in each field are varchar(45) representing an email address and a keyword. It is possible that the information collected may duplicate itself as it is related to site browsing data collection. To avoid duplicate entries, I used tried to use INSERT IGNORE into, REPLACE into, and finally I'm trying the following:
insert into <table name> (user_email, key_token) values ('<email>#<this>.com', 'discountsupplies') on duplicate key update user_email='<email>#<this>.com',key_token='discountsupplies';
but I am still seeing duplicate records being inserted into the table.
The SQL that generated the table:
DROP TABLE IF EXISTS `<database name>`.`<table name>` ;
CREATE TABLE IF NOT EXISTS `<database name>`.`<table name>` (
`user_email` VARCHAR(45) NOT NULL ,
`key_token` VARCHAR(45) NOT NULL,
PRIMARY KEY (`user_email`, `key_token`) )
ENGINE = InnoDB;
While I saw several questions that were close to this one, I did not see any that addressed why this might be happening, and I'd like to figure out what I'm not understanding about this behavior. Any help is appreciated.
As an addendum, After adding the UNIQUE KEY statements, I went back and tried both REPLACE and INSERT IGNORE to achieve my goal, and none of these options is excluding duplicate entries.
Also adding: UNIQUE INDEX (user_email, key_token)
doesn't seem to help either.
I'm going to do this check via a manual look-up routine until I can figure this out. If I find an answer I'll be happy to update the post.
Added Unique Index lines below the original create table statement -
-- -----------------------------------------------------
-- Table `<db name>`.`<table name>`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `<db name>`.`<table name>` ;
CREATE TABLE IF NOT EXISTS `<db name>`.`<table name>` (
`user_email` VARCHAR(45) NOT NULL ,
`key_token` VARCHAR(45) NOT NULL,
PRIMARY KEY (`user_email`, `key_token`),
UNIQUE KEY (user_email),
UNIQUE KEY (key_token)
)
ENGINE = InnoDB;
CREATE UNIQUE INDEX ix_<table name>_useremail on `<db name>`.`<table name>`(user_email);
CREATE UNIQUE INDEX ix_<table name>_keytoken on `<db name>`.`<table name>`(key_token);
it seems to be ok (no errors when creating tables during the source step), but I'm still getting duplicates when running the on duplicate query.
You have a composite primary key on both columns.
This means that it's the combination of the fields is UNIQUE, not each field as is.
Thes data are possible in the table:
1#example.com 1
2#example.com 1
2#example.com 2
, since no combination of (user_email, key_token) repeats in the table, while user_email and key_token as themselves can repeat.
If you want each separate column to be UNIQUE, define the UNIQUE constraints on the fields:
CREATE TABLE IF NOT EXISTS `<database name>`.`<table name>` (
`user_email` VARCHAR(45) NOT NULL ,
`key_token` VARCHAR(45) NOT NULL,
PRIMARY KEY (`user_email`, `key_token`),
UNIQUE KEY (user_email),
UNIQUE KEY (key_token)
)
ENGINE = InnoDB;
Update
Having duplicates in a column marked as UNIQUE would be a level 1 bug in MySQL.
Could you please run the following queries:
SELECT user_email
FROM mytable
GROUP BY
user_email
HAVING COUNT(*) > 1
SELECT key_token
FROM mytable
GROUP BY
key_token
HAVING COUNT(*) > 1
and see if they return something?
PRIMARY KEY (user_email,key_token) means a combination of both will be unique but if you also want individual email and key_tokens to be unique you have to use UNIQUE seperately for each column..
PRIMARY KEY ('user_email', 'key_token'),
UNIQUE KEY (user_email),
UNIQUE KEY (key_token)
final solution for now: query table to get list of key_tokens by user_email, test current key_token against list entries, if found don't insert.
Not optimal or pretty, but it works....
To me it looks like you selected composite Primary Key solely for performance reasons where it should be an index like so
CREATE TABLE IF NOT EXISTS `<database name>`.`<table name>` (
`user_email` VARCHAR(45) NOT NULL ,
`key_token` VARCHAR(45) NOT NULL,
PRIMARY KEY (`user_email`),
INDEX (`user_email`, `key_token`)
)
Of course if you are concerned about getting a duplicate key_token you'll still need a unique index.
Sorry I'm awfully late to reply, but perhaps someone will stumble on this like I have :)