I'm relatively new to databases, database architecture and MySQL in general, so forgive me if my method isn't optimal. I have created a table called comments, for which i want to store the users id in to the column post_id which works fine as it stands. This table's sole purpose is to store messages posted on any given users profile and some other related information.
However i want to allow duplicate entries so i can read the comments table and look for a certain users id, then take the column comments from the comments table and display them on the users profile where the id is matched.
I'd do this by doing an INNER JOIN on comments and user_info, specifically the post_id from comments and id from user_info.
When posting the information to the database from a users profile, i get the below error
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '59' for key 'post_id'
user_info
'user_info', 'CREATE TABLE `user_info` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n `username` varchar(12) COLLATE utf8_unicode_ci NOT NULL,\n `pass` varchar(40) COLLATE utf8_unicode_ci DEFAULT NULL,\n `joined` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `username` (`username`)\n) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'
comments
'comments', 'CREATE TABLE `comments` (\n `post_id` int(11) DEFAULT NULL,\n `comment` text COLLATE utf8_unicode_ci,\n `date_posted` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n UNIQUE KEY `post_id` (`post_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'
Let me know if i'm missing anything that would potentially help you answer the question.
You need to drop unique index on post_id:
ALTER TABLE `comments` DROP INDEX post_id;
Then you can create a non-unique index if necessary:
ALTER TABLE `comments` ADD INDEX `post_id` (`post_id`);
Also your comments table lacks primary key. Create an auto increment integer column: comment_id, that would identify the record.
post_id should be an auto increment field as primary key in columns table that uniquely identifies a tuple (row). You should add another column called user_id that stores the id from user_info table. You should then add an index to user_id column in comments table for faster search.
You need three tables: users, comments, and commentlist.
The users table just holds user details. The comments table just holds comments. Commentlist has 3 columns: an id, which you won't really use, user id, and comment id.
You can then have multiple user id entries in the commentlist table, all relating to an individual comment id.
You have to check your query once again. Because the problem may exits on your select query. You have to remove unique index from post_id column of comments table.
If a row in comment is to be related to a row in user_info, and one user_info can have zero, one or more comment, and comment is related to exactly one user_info...
Then you'd store the value of the id column (PRIMARY KEY) column from the user_info, as a value in the comment table.
With the InnoDB engine, you can also define a FOREIGN KEY constraint.
For example:
ALTER TABLE `comment` ADD
`user_info_id` INT COMMENT 'FK ref user_info.id' ;
ALTER TABLE `comment` ADD
CONSTRAINT `FK_comment_user_info` FOREIGN KEY (`user_info_id`)
REFERENCES `user_info` (`id`)
ON UPDATE CASCADE
ON DELETE CASCADE
Leave current the post_id column as a unique key (or change it to a PRIMARY KEY) on the table.
Note that a JOIN operation will return multiple rows, when there are multiple comments for a given user_info
FROM `user_info` u
LEFT
JOIN FROM `comment` c
ON c.user_info_id = u.id
WHERE u.id = 42
If a comment can be related to more than one user_info, then that would be a many-to-many relationship, and the normative pattern to implement that would be to add a third association table, a row in the association table would have foreign key references to both the user_info table and the comment table.
Related
I am trying to figure out relationships and deletion options.
I have two tables, User and UserStaff, with a 1:n relationship from User to UserStaff (a user can have multiple staff members).
When my User is deleted, I want to delete all of the UserStaff tables associated with that User. When my UserStaff is deleted, I don't want anything to happen to User. I understand that this is a cascading relationship, but I'm not sure which way.
i.e. Do I select the existing foreign key in my UserStaff table and make it cascading, or do I create a new foreign key in User and set that to cascading?
Yes, it's possible. You should make the FK in UserStaff table. In this way:
User Table
CREATE TABLE `User` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
UserStaff Table
CREATE TABLE `UserStaff` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`UserId` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`Id`),
KEY `UserId` (`UserId`),
CONSTRAINT `UserStaff_ibfk_1`
FOREIGN KEY (`UserId`)
REFERENCES `User` (`Id`)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
From Wikipedia:
CASCADE
Whenever rows in the master (referenced) table are deleted (resp. updated), the respective rows of the child (referencing) table with a matching foreign key column will get deleted (resp. updated) as well. This is called a cascade delete (resp. update[2]).
Here, User is the master table, and UserStaff is the child table. So, yes, you'll want to create the foreign key in UserStaff, with ON DELETE CASCADE
It's been a while since I've used this, but here goes (btw, I use Toad for MySql - a great IDE, and it's free too - http://www.toadworld.com/Freeware/ToadforMySQLFreeware/tabid/561/Default.aspx!)
You need to add a Constraint to the User table. If you have an id column (and the corresponding foreign userid key in UserStaff) then the SouceColumn should be id, the destination table UserStaff and the destination column userid. You can then set the OnDelete action to be 'Cascade'
The other options are pretty self-explanatory - Restrict limits values to the values in the source column, Set Null sets the foreign key matches to Null and No Action does, er, nothing.
This stuff is very easy to do via the Toad IDE. I used MySqlAdmin tools for ages but recently discovered Toad (and it has diff and compare tools too!).
The ON DELETE CASCADE is specified on the foreign key in the UserStaff table. For additional info on foreign keys the MySQL documentation has a number of examples. The User table does not have a foreign key pointing to UserStaff, so it will not be affected by changes to the UserStaff table.
The easiest way might be to make two quick tables and try it out. But since you didn't I can tell you that the outcome will be that it work the way that you want to.
When you have a table User and a table UserStaff were a field in UserStaff uses a foreign key to reference a field in User; then if you delete a record from UserStaff that will be removed wihtout having any affect on the User table. The other way around will delete all records related to that record.
Short version: A field in UserStaff should reference a field in User with CASCADE
I have user account system which needs sub-account system. So there is the "main account" and accounts that can acces the main account data. I was thinking of users table where is field "subaccount" true or false. And then according to "parent-account" field open data from account id that the "parent-account" references to.
How do you feel about this?
SELECT * FROM users WHERE id = :id
if("SUBACCOUNT" exists){
SELECT * FROM users WHERE id= :parentaccount
echo parentaccounddata
$_session['parentaccount'] = false; //restrict certain features
}
If every account has only one parent it is a quite straight forward implementation. You want to create a user table with an id and a parent column. If the account is a main account you can just set the parent column to NULL. If it is a sub account you can set it to the id.
Table Creation
CREATE TABLE IF NOT EXISTS `accounts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
-- other parameters like name, ip, ...
`parent` int(11) NULL,
PRIMARY KEY (`id`),
FOREIGN KEY(`parent`) REFERENCES `accounts`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) AUTO_INCREMENT=1 ;
To check if it is a main account or not just check if the parent column is set to NULL or not.
It is possible to have a table reference its own primary key via foreign key constraint (at least it is in MySQL, but I see no reason why other SQL based databases wouldn't allow it).
However I'd like to propose alternative solution. Adding new table which will connect accounts:
CREATE TABLE `accountConnection` (
`accountId` int NOT NULL,
`subAccountId` int NOT NULL,
PRIMARY KEY (`accountId`, `subAccountId`),
CONSTRAINT `fkAccountId` FOREIGN KEY (`accountId`) REFERENCES `account` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fkSubAccountId` FOREIGN KEY (`subAccountId`) REFERENCES `account` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I used MySQL for the example. This way you allow many to many relationship between accounts and sub-accounts, which I'm not sure if you need from your question, but is very flexible solution, even if you don't need it right now, should you ever need to change your system to allow for it
I am working on a CMS system (largely as a learning exercise) for a private website. Atm I have three tables: one for articles, one for tags and a joining table so that each article can have multiple tags.
The table I am having issues with consists of three columns -
article_tags: id (auto_increment), article_id, tag_id
My problem stems from the fact that an article can appear any number of times, and a tag can also appear any number of times, however a given combination of the two should only appear once - that is, each article should only have one reference to any single tag. Currently it is possible to INSERT "duplicate" rows where the id is different, but the combination of article_id and tag_id are the same:
id , article_id, tag_id
1 1 1
2 1 2
3 2 1
4 1 1 <- this is wrong
I could check in PHP code for a record that contains this combination, but I'd prefer to do it in sql if possible (if it is not, or it is undesirable then I will do it using PHP). Due to the id being different and the inability to set unique columns things like INSERT IGNORE and ON DUPLICATE do not work.
I'm quite new to mySQL so if I'm doing something silly please point me in the right direction.
Thanks
You should review your table definition.
You can (from best to worst):
Add a composite primary key on (article_id and tag_id) and remove auto_increment (previous primary key)
Add an index (UNIQUE) on (article_id and tag_id) and keep your auto_increment primary key
Select distinct in php: SELECT DISTINCT(article_id, tag_id) FROM
... without changing anything in your table
Right now, your table is defined as something like this:
CREATE TABLE IF NOT EXISTS `article_tags` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`article_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
The best solution (option 1) would be to remove your current (auto_increment) primary key and add a primary key (composite) on columns article_id and tag_id:
CREATE TABLE IF NOT EXISTS `article_tags` (
`article_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`article_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
But (option 2) if you absolutely want to keep your auto_increment primary key, add an index (unique) on your columns:
CREATE TABLE IF NOT EXISTS `article_tags` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`article_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `article_id` (`article_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Anyway, if you don't want to change your table definition, you could always use DISTINCT in your php query:
SELECT DISTINCT(article_id, tag_id) FROM article_tags
Such many-to-many relationship tables, sometimes called join tables, often have just two columns, and have a primary key that's a composite of the two.
article_id
tag_id
pk = (article_id, tag_id)
If you change the definition of that table you will definitively solve that problem.
How should you order the columns in composite keys? It depends on how your application will look up items in the join table. If you'll always start with the article_id and look up the tag_id, then you put the article_id first in the key. The DBMS can random-access values for the first column in the key, but has to scan the index to find values in second (or subsequent) columns in the key.
You may want to create a second index on the table, (tag_id, article_id). This will allow fast lookups based on the tag_id. You may ask, "why bother to put both columns in the index?" That's to make the index into a covering index. In a covering index, the desired value can be retrieved directly from the index. For example, with a covering index,
SELECT article_id FROM article_tag WHERE tag_id = 12345
(or a JOIN that uses similar lookup logic) only needs to access the index on the disk drive to get the result. If you don't have a covering index, the query needs to jump from the index to the data table, which is an extra step.
Join tables typically have very short rows (a couple of integers) so the duplicated data for a couple of covering indexes (the primary key and the extra one) isn't a big disk-space hog.
create table Board (
boardID char(30) not null,
readLevel tinyint not null,
writeLevel tinyint not null,
PRIMARY KEY (boardID) ) engine=InnoDB character set=utf8;
create table Post (
postID int not null AUTO_INCREMENT,
title char(50) not null,
content TEXT not null,
writeDate date not null,
readCount int not null,
PRIMARY KEY (postID)) engine=InnoDB character set=utf8;
create table Save_Board_Post(
boardID char(30) not null,
postID int not null,
FOREIGN KEY (boardID) REFERENCES Board(boardID) ON UPDATE CASCADE,
FOREIGN KEY (postID) REFERENCES Post(postID) ON UPDATE CASCADE ) engine=InnoDB character set=utf8;
insert into Board (boardID, readLevel, writeLevel) values ('testBoard', 0, 0);
insert into Post (title, content, writeDate, readCount) values ('testPost1', 'test', CURRENT_TIMESTAMP() ,0);
select * from Board where boardID='testBoard';
select * from Post where tile='testPost1';
select * from Save_Board_Post where boardID='testBoard';
I'm rookie in sql. and I'm not native about English.
So, Please forgive my English skills.
Here's my mysql code.
Last five lines are for test. And select from Board and Post is working fine.
But
select * from Save_Board_Post where boardID= 'testBoard';
It doesn't work. This code has no error. but there is no output result.
I guess it means no data in Save_Board_Post table.
I thought REFERENCES command is automatically creation data when insert parent table.
If it does not, please let me know how to automatically creation in relation data.
No, that's not what REFERENCES does. All that your REFERENCES constraints mean is that every row that is inserted (manually) into the Save_Board_Post table must have a boardID and a postID that exist in the Board and Post tables. Nothing is inserted into that table automatically.
If you are trying to represent what board a post is in, the appropriate way to do this would be to make the board ID be a property of the post, e.g.
CREATE TABLE Post (
postID INTEGER NOT NULL AUTO_INCREMENT,
boardID CHAR(30) NOT NULL,
...
FOREIGN KEY (boardID) REFERENCES Board(boardID)
);
rather than having an entirely separate table just for that data.
You cannot automatically insert data in child table by inserting in the parent table. You got it right when you said it failed because there was no data in the table. Referential integrity exist to remove redundancy in a database. I dont think there is a way for you to automatically insert into the child table by inserting into a parent table. you have to do it manually.
Below I have two tables
users and users_profiles
Both are innoDB and collation: utf8_general_ci
They are as follows:
users
CREATE TABLE `users` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`status` char(10) NOT NULL,
`username` varchar(15) NOT NULL,
`email` varchar(50) NOT NULL,
`password` char(32) NOT NULL,
`reg_date` int(11) NOT NULL,
`ip` varchar(39) DEFAULT NULL,
PRIMARY KEY (`uid`),
UNIQUE KEY `username` (`username`,`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
users_profiles
CREATE TABLE `users_profiles` (
`uid` int(11) NOT NULL,
`first_name` varchar(40) DEFAULT NULL,
`last_name` varchar(50) DEFAULT NULL,
`gender` char(6) DEFAULT NULL,
`website` varchar(100) DEFAULT NULL,
`msn` varchar(60) DEFAULT NULL,
`aim` varchar(60) DEFAULT NULL,
`yim` varchar(60) DEFAULT NULL,
`twitter` varchar(15) DEFAULT NULL,
UNIQUE KEY `uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
After creating the tables in phpmyadmin I created a foreign key on the users_profiles table, code below is what phpMyAdmin created.
As follows:
ALTER TABLE `users_profiles`
ADD CONSTRAINT `users_profiles_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `users` (`uid`) ON DELETE CASCADE ON UPDATE CASCADE;
basically the users_profiles.uid is a foreign key and references the users.uid
In phpMyAdmin I go to insert and fill in some sample data leaving the uid obviously to auto increment. When i have inserted a record in users table I goes into the users_profiles table and notice the users.uid is not inserted automatically in the users_profiles,
Is this normal?
Reason is when someone for example registers on a form, they will be asked for username, email and password, and i do a query in php to insert that data into users table, i thought that because i have a foreign key that it would also automatically insert a row in the users_profiles table with the uid from users table so there is a link between the user and there profile. But when i insert a record into users table the users.uid is not inserted into the users_profiles table.
I tried another example to see what would happen and this one works as i would expect due to the cascade on update and delete.
If i insert a row in users table and then manually insert the users.uid into users_profiles.uid (they are now linked) and add for example my first_name and last_name then in phpmyadmin delete the user from users table it deletes the row in the users_profiles table. This works like it should obviously as i don't want a user to be deleted and have there profile remain.
This has confused me as when I do create a form and a user signs up, they essentially would not have a profile because on signup no profile is created for them with there users.uid in the user_profiles table (no link between them) although I have a foreign key.
Can some explain why it's not working as I expect, maybe it should be working like I want it to but something is wrong or I am missing the whole point otherwise.
UPDATE
In reference to reply from #Mark Wilkins
I understand what you mean now. But something I am not 100% sure on is this:
User signs up, a record is created in users table; they login and visit profile page where the can fill it in and submit the form.
On processing the form am I right in thinking I would need to do the following:
user filled in profile form and submitted (first time they submitted profile as they are a new user), after validating data etc I first check to see if the uid in the users table match a uid in the users_profile table, if there's a match then UPDATE record with new values (this would mean the user has previously filled in there profile as on signup they don't have one) but if no match is found on uid from both tables then I would perform an INSERT query because no profile yet exists for the user. I take it that obviously I would store the uid from users table in session with other data on successful login and the uid in session would be the uid that is inserted into the users_profiles table in column uid? That way a link is created between two tables and if I now decide to delete the user there profile will also be deleted to.
Foreign key constraints are not designed to create rows. Their purpose is to ensure data integrity by forcing that a value in a child table that references a parent table value actually exists in that parent table and prevents a parent row from being deleted that has references to it in a child table.
On insert, the calling code must write rows into the two tables (first users then profiles).
If I followed the description correctly, it is working as expected. A foreign key relationship basically says that a parent must exist for a given child (a user must exist for a given user_profile in your example). It does not require the opposite (that a user_profile record exist for a user). It will never result in an INSERT being performed on the child table. You have to insert the record into the user_profile table and the foreign key relationship will guarantee that it is maintained.
Edit for the additional OP info: In general, yes I believe that is the thing you want to do. I have not dealt enough with web development to know if that particular process is correct. In either case, though (whether or not a profile record has been created), you will need to know which user to modify. My opinion about this, however, would be to create the associated user_profile record directly after creating the user record (just leave the informational fields empty in it). That way you know it exists when the go to edit it and you don't have to perform a MERGE style operation.