SQL refer a to b and b to a - mysql

I'm using PHPMyAdmin (so mySQL) to create this.
I have 2 tables, Album and photo. Of course, a photo can be part of an album and for that I use a foreign key from photo to album.
But now I want to be able to put a photo in the album table so I can use that photo as a cover for my album. I've tried adding a foreign key but that gives me a foreign key constraint.
Here are the tables to help understand what I mean (foto = photo). The red line indicates what I want to achieve.
I'm not that good at SQL so any help is appreciated.
SQL Album:
CREATE TABLE `Albums` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Album_Naam` varchar(255) NOT NULL,
`Aanmaakdatum` datetime NOT NULL,
`FotoID` int(11) DEFAULT '1',
PRIMARY KEY (`ID`),
KEY `FotoID` (`FotoID`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1
SQL Photo
CREATE TABLE `Foto` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Foto_Naam` varchar(255) NOT NULL,
`AlbumID` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `AlbumID` (`AlbumID`),
CONSTRAINT `Foto_ibfk_1` FOREIGN KEY (`AlbumID`) REFERENCES `Albums` (`ID`)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=latin1

A foreign key can be made optional by making the referencing field nullable; NULL values in the field do not violate the foreign key constraints enforced on it. This can be used to represent purely optional relations or, as in the case of this question, defer setting the value in semi-cyclic dependencies.
Note that to remove the cover photo from an album, the album will first need it's cover reference to that foto set to another photo, or to null. Similarly, to delete the album, you would need to delete it's Fotos, and so first set it's cover to null.

Related

Order of deletion with foreign key constraints,

I have a schema with three tables and foreign key 'On Delete' constraints as below:
| -> FK (cascade) -> |
Organisation | | Users
| - FK (cascade) Categories -> FK(restrict) -> |
If I delete an organisation I want to delete the users and the categories related to it, but I can't allow a category to be deleted if a user refers to it except in the case where the whole organisation is being deleted.
At present if I delete an organisation the category deletion fails if there's a user referring to it. This seems to indicate that MySQl is processing the foreign key constraints on the Categories table before the Users table.
This wouldn't be a problem if the Users in the user table were cleared before the Categories.
Is there a way to tell MySQl what order to process these FK constraints so that the tables get cleared in a specified order?
Note: I could add some code to explicitly clear the user table first, but that's fiddly within the design of the code so I don't want to go there yet.
Note also that the required security limits what I can do with the schema on-the-fly, so changing the FK constraints or disabling checking of them is not really an option. I can change the security to make a one-time change. I don't want to loosen security permanently unless there's no other way. Writing extra code as above is preferred
Here are the Create statements for the tables, edited to remove unrelated fields.
CREATE TABLE `organisation` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`orgGUID` varchar(36) NOT NULL,
`archivedFlag` tinyint(3) unsigned NOT NULL DEFAULT '0',
`orgName` varchar(45) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
UNIQUE KEY `org_guid_UNIQUE` (`orgGUID`)
) ENGINE=InnoDB AUTO_INCREMENT=83 DEFAULT CHARSET=utf8;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userGUID` varchar(36) NOT NULL,
`name` varchar(45) NOT NULL,
`orgGUID` varchar(36) NOT NULL,
`userType` smallint(6) DEFAULT NULL,
`PwHash` varchar(255) DEFAULT NULL,
`ethnicityGUID` varchar(36) DEFAULT NULL ,
`genderGUID` varchar(36) DEFAULT NULL ,
`yearGroupGUID` varchar(36) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
UNIQUE KEY `userGUID_UNIQUE` (`userGUID`),
KEY `fk_user_org_idx` (`orgGUID`),
KEY `fk_ethnicity_category_idx` (`ethnicityGUID`),
KEY `fk_gender_category_idx` (`genderGUID`),
CONSTRAINT `fk_ethnicity_category` FOREIGN KEY (`ethnicityGUID`) REFERENCES `categories` (`id`) ON UPDATE NO ACTION,
CONSTRAINT `fk_gender_category` FOREIGN KEY (`genderGUID`) REFERENCES `categories` (`id`) ON UPDATE NO ACTION,
CONSTRAINT `fk_user_org` FOREIGN KEY (`orgGUID`) REFERENCES `organisation` (`orgGUID`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=162 DEFAULT CHARSET=utf8;
CREATE TABLE `categories` (
`id` varchar(36) NOT NULL,
`orgGUID` varchar(36) NOT NULL,
`categoryType` varchar(20) NOT NULL,
`category` varchar(45) NOT NULL,
`priority` int(11) NOT NULL,
`analysisCode` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `fk_category_org_idx` (`orgGUID`),
CONSTRAINT `fk_category_org` FOREIGN KEY (`orgGUID`) REFERENCES `organisation` (`orgGUID`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Officially, you have no control over the order of the cascaded operations. You may be able to abuse some undocumented behaviour however:
for MySQL 5.5, the foreign keys are executed in the order they got created, so dropping and recreating the fk_category_org-constraint should work
for MySQL 5.6+, the foreign keys are executed in the lexical order of their names, so renaming fk_category_org to e.g. fk_z_category_org should work
This is undocumented and can change anytime (and might be influenced by other factors).
That being said, the proper way to do this (and anything else too complicated for on cascade) would be to add a before delete-trigger on your organisation-table that "manually" deletes the users first and then the categories afterwards. before delete-triggers are executed before on cascade (so you can decide if you want to keep those or not, although it would probably be misleading).
It is not entirely clear if that is your intented behaviour, but currently, a user can have a category that belongs to organization 1 while he is assigned to organization 2. Deleting organization 1 would then still fail. It looks a bit as if that is what you want to prevent by your design, but if you want the deletion to work in this case too, you need to use the trigger to be able to incorporate that (or manually delete it in your application), cascading will not work unless you also cascade in the category table.

Simple Relation between 2 tables

I have a problem here.
I cannot add this to my db because one table is dependent of another and vice-versa.
So I get
Cannot add foreign key constraint
on the first create table that I put
How can I add this 2 tables if they both have constraints??
-- User Roles
CREATE TABLE IF NOT EXISTS `user_roles` (
`user_role_id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(45) NOT NULL,
`role` varchar(45) NOT NULL,
PRIMARY KEY (`user_role_id`),
UNIQUE KEY `uni_username_role` (`role`,`username`),
UNIQUE KEY `ix_auth_username` (`username`,`role`),
KEY `fk_username_idx` (`username`),
CONSTRAINT `fk_username` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
-- Users
CREATE TABLE IF NOT EXISTS `users` (
`username` varchar(45) NOT NULL,
`name` varchar(45) DEFAULT NULL,
`hashedPassword` varchar(500) NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '1',
`image` mediumblob,
`team` int(11) DEFAULT NULL,
`userRole` int(11) DEFAULT NULL,
PRIMARY KEY (`username`),
KEY `fkteam_idx` (`team`),
KEY `fkrole_idx` (`userRole`),
CONSTRAINT `fkrole` FOREIGN KEY (`userRole`) REFERENCES `user_roles` (`user_role_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `fkteam` FOREIGN KEY (`team`) REFERENCES `team` (`idteam`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
To do this, you'll need to use deferrable constraint checks, but unfortunately MySQL does not implement this standard SQL feature.
As far as I know, only Oracle and PostgreSQL support this feature (deferrable constraints). These constraints are checked at the end of the transaction, and not on every single row insertion. That would solve your problem.
Therefore, you have two options:
Switch to Oracle or PostgreSQL (unlikely, I guess) or,
Change your table definition to allow one of the foreign key constraints to accept null values.
In the second case, you would:
Insert in the table that allow null in the FK, getting the generated ID.
Insert in the other table using the ID. Then, get the second generated ID.
Update the null in first table using the second ID.
Commit.
That's it.

Can't Constrain Both Junction Table Columns

I have a juction table that contains two foreign keys (from Profiles and Districts tables), with both columns as a composite primary key.
`profID` int(11) NOT NULL,
`distID` varchar(8) NOT NULL,
PRIMARY KEY (`profID`,`distID`)
I'd like to constrain both columns, but MySql throws an error:
#1050 - Table './database_name/z#002dprof#002ddist' already exists
In troubleshooting the problem, I've tried creating another duplicate junction table from scratch, but I get the same error. Oddly, MySQL will allow me to constrain one column or the other, but not both columns. I'm stumped, since I have other (non-junction) tables that have constraints on more than one foriegn key column.
By the way, I'm using phpMyAdmin, and all tables are InnoDB with utf-8.
Any help would be appreciated.
ADDED: SHOW CREATE TABLE results
CREATE TABLE `Profiles` (
`profID` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(64) NOT NULL,
`stID` varchar(2) NOT NULL,
`zip` varchar(5) NOT NULL,
PRIMARY KEY (`profID`),
KEY `stID` (`stID`,`zip`),
KEY `email` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=utf8
CREATE TABLE `Districts` (
`distID` varchar(8) NOT NULL,
`stID` varchar(2) NOT NULL,
`abbrev` varchar(16) NOT NULL,
PRIMARY KEY (`distID`),
KEY `stID` (`stID`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `z-prof-dist` (
`profID` int(11) NOT NULL,
`distID` varchar(8) NOT NULL,
PRIMARY KEY (`profID`,`distID`),
KEY `distID` (`distID`),
KEY `profID` (`profID`),
CONSTRAINT `z-prof-dist_ibfk_1` FOREIGN KEY (`distID`) REFERENCES `Districts` (`distID`)
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
I think I found a fix. Rather than using the phpMyAdmin function for adding a constraint (where I kept getting the error message), I instead followed marekful's lead by using an SQL ALTER TABLE query (with a new constraint name) as such:
ALTER TABLE `z-prof-dist`
ADD CONSTRAINT `test1`
FOREIGN KEY (`profID`) REFERENCES `Profiles` (`profID`)
ON UPDATE CASCADE
I still don't understand the cause of the original error, but I can see that the newly added foreign key constraint is working perfectly.

Can't link two tables?

I'm sure this is something ridiculously simple, but I can't get my head around it.
Every time I try running this script, I get error number 150. I know that this is a foreign key issue. My other tables are fine and link to the projectregister table with no problems, but for some reason nothing wants to link to the userchar table.
I'm running this on a college server, so I cant try show engine innoDB status.
Any ideas what's wrong here?
Thanks
CREATE TABLE `userchar` (
`userid` int(5) NOT NULL,
`charname` varchar(25) NOT NULL,
`charstats` varchar(255) DEFAULT NULL,
PRIMARY KEY (`userid`,`charname`),
CONSTRAINT `userchar_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `projectregister` (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `notes` (
`userid` int(5) NOT NULL DEFAULT '0',
`charname` varchar(25) NOT NULL,
`usernote` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`userid`,`charname`,`usernote`),
CONSTRAINT `notes_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `projectregister` (`userid`),
foreign key (charname) references userchar(charname)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
A foreign key must reference a unique value - be it a primary key or a plain old unique index.
Here, you are attempting to make notes.userid reference projectregister.userid. However, projectregister.userid is not a unique value - only the combination of projectregister.userid and projectregister.charname is unique.
You should either change the primary key or the foreign key definitions so that their column lists match.

How do I add a foreign key to a table in Sequel Pro?

I am trying to add a foreign key to a table in Sequel Pro (using the UI).
I have two tables: "titles" and "categories" as below:
CREATE TABLE `titles` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` tinytext NOT NULL,
`category` varchar(256) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `category` (
`key` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(256) NOT NULL DEFAULT '',
PRIMARY KEY (`key`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
I want to create a foreign key, but nothing I try works.
The category table should be a simple lookup table. I want to assign each title a category from about 6 - 8 different choices.
Originally I had the category fields as tinytext, but I would get the error:
"MySQL Error 1170 (42000): BLOB/TEXT Column Used in Key Specification Without a Key Length".
Searched here and discovered you can't use text field that way, so I switched to Varchar and added a length of 256. Now I get:
MySQL said: Can't create table 'lit.#sql-2bf3_2' (errno: 150).
How can I create a foreign key for my table?
In Access this is pretty easily done. Somehow Access associates the unique key in the table with the lookup, but then hides the key and shows you the text field instead. How can I get a similar result with Sequel Pro and MySQL?
EDIT:
So, to clarify this is where I'm at right now. I've added an index on the category field in the titles table (first picture).
I've changed the "key" field in the category table to CategoryID (second picture).
However, I still can't seem to create the relationship between the two tables. I get the same error
As category will be your lookup table off of titles, you'd need to create an index on category which would refer to the foreign key. They would both need to be the same datatype (usually an INT, though sometimes you could use a CHAR(2) variable in some cases, but usually not necessary). Since you only expect 6-8 categories, I'd make it INT(1) (or may be INT(2) to be safe).
In this case, you would need to create something like categoryId which would first need to be indexed, then connect to the foreign key on categorywhich does not appear to exist; I'm not sure you want to use a term like key. Why not just make categoryId the primary key on category? this way when you create the foreign key on titles with the same name, it should link up fine.
Edit:
To clarify a little, after you've created categoryID on category you can do this under titles
ALTER TABLE Orders
ADD CONSTRAINT fk_categoryID
FOREIGN KEY (`categoryId`) REFERENCES `category`(`categoryId`)
Edit:
Here's a modification using your original layout. this should work for you:
CREATE TABLE IF NOT EXISTS `category` (
`key` int(2) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(256) NOT NULL DEFAULT '',
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
CREATE TABLE IF NOT EXISTS `titles` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` tinytext NOT NULL,
`categoryID` int(2) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `categoryID` (`categoryID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
ALTER TABLE `titles`
ADD CONSTRAINT `titles_ibfk_1` FOREIGN KEY (`categoryID`) REFERENCES `category` (`key`);