Database model for hierarchy Country - Organization - Suborganization - User - mysql

Good day, I need to create an organizational structure which should support following hierarchies:
Country - Organization - User
Country - Organization - Suborganization - User
I thought about having three tables. Table Country (column id is three-letter ISO 3166-1 alpha-3):
CREATE TABLE `country` (
`id` CHAR(3) NOT NULL,
`country` VARCHAR(40) NOT NULL,
PRIMARY KEY (`id`)
);
Table Organization:
CREATE TABLE `organization` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`country_id` CHAR(3) NOT NULL,
`parent_id` INT(11) NULL DEFAULT NULL,
`name` VARCHAR(40) NOT NULL,
PRIMARY KEY (`id`),
INDEX `idx_organization_1` (`parent_id` ASC),
CONSTRAINT `fk_customer_country_1`
FOREIGN KEY (`country_id`)
REFERENCES `country` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `fk_organization_organization_1`
FOREIGN KEY (`parent_id`)
REFERENCES `organization` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE
);
Table User:
CREATE TABLE `user` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`organization_id` INT(11) NULL DEFAULT NULL,
`username` VARCHAR(45) NOT NULL,
`password` CHAR(32) NOT NULL,
PRIMARY KEY (`id`),
INDEX `idx_user_organization_1` (`organization_id` ASC),
CONSTRAINT `fk_user_organization_1`
FOREIGN KEY (`organization_id`)
REFERENCES `organization` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE);
Table Country exists to prevent an organization be assigned an unsupported country code. I don't want to use MySql's enum. Although I'm working with MySql currently I need my schema to be independent on the specific DB.
Is this good design or is there a better one? I doubt because of following questions. I can do it "somehow" but I'd like to know best practice approach.
A suborganization has a country code foreign key but country code is allready specified in the parent organization. I feel it's a kind of duplicity.
If I delete a row in table country will cascade delete fail if the DB attempts to delete suborganization before deleting it's parent organization?
Is null in foreign keys supported by all DBs? Organizations have null parent_id but parent_id is used in the self referencing forign key fk_organization_organization_1.
Country code used in foreign keys is CHAR(3). Would it be faster is it would be INT(3)?
I'd appreciate any ideas. Many thanks. Vojtech

I would make country_id nullable to avoid such duplication. Also
you can implement some application-level control to assure that at
least one of (country_id, parent_id) is not null.
Deletion must
be successfull as long as the both references defined with "ON
DELETE CASCADE" clause
All the DBs I have worked with support
nullable foreign keys: Oracle, DB2, MySQL, SQL Server, Postgre
The difference is most probably negligible

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.

mysql one to many relation is not working

Having some trouble adding a relation between tables
CREATE TABLE IF NOT EXISTS `employees` (
`emp_id` int(11) NOT NULL auto_increment,
`emp_fname` char(50) NOT NULL,
`emp_sname` char(50) NOT NULL,
`emp_patr` char(50) NOT NULL,
`emp_birth` datetime NOT NULL default CURRENT_TIMESTAMP,
`isInCharge` boolean default NULL,
`depart_id` int(11) NOT NULL,
PRIMARY KEY (`emp_id`)
) ENGINE=InnoDB DEFAULT CHARSET=cp1251;
CREATE TABLE IF NOT EXISTS `department` (
`depart_id` int(11) NOT NULL auto_increment,
`depart_name` char(50) NOT NULL,
PRIMARY KEY (`depart_id`),
FOREIGN KEY (depart_id) REFERENCES employees (depart_id) on delete
cascade on update cascade
) ENGINE=InnoDB DEFAULT CHARSET=cp1251;
Cannot add foreign key constraint
What is going wrong? I need to make possible for many employees to be in one department.
Also, how can I generate a random birth date from 1950 to 1990?
Your foreign key is in the wrong place. It belongs in the employees table not the department table. And it should be:
FOREIGN KEY (depart_id) REFERENCES department(depart_id) on delete cascade on update cascade
This should be easy to remember. You define the primary key in the table where the column is unique. You define the foreign key in all tables that refer to that primary key.
It should be the other way around right? dept_Id in Employee shall be foriegn key referencing the primary key Dept_ID of Department.

MySQL:ERROR 1215: Cannot add foreign key constraint

I've been trying to follow a tutorial that I've came across, which is about spring security. In some place, I need to create 2 tables in my database which is user and authorities. While doing this, I am using this script that is suggested like in the tutorial. http://docs.spring.io/spring-security/site/docs/3.0.x/reference/appendix-schema.html
I've already a user table in my db, so I just need to add autohirites table. Since I'm using MySQL, I've changed that query like below:
create table authorities (
username varchar(70) not null,
authority varchar(50) not null,
CONSTRAINT fk_authorities_users foreign key(username) references user(userFirstName));
create unique index ix_auth_username on authorities (username,authority);
Also, here is my user table too:
CREATE TABLE `user` (
`userId` INT(11) NOT NULL AUTO_INCREMENT,
`userFirstName` VARCHAR(70) NOT NULL,
`userLastName` VARCHAR(70) NOT NULL,
`userEmail` VARCHAR(70) NOT NULL,
`userAddress` VARCHAR(500) NULL DEFAULT NULL,
`userPhoneNumber` INT(13) NULL DEFAULT NULL,
`isActive` BINARY(50) NULL DEFAULT '1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0',
`userPassword` VARCHAR(50) NOT NULL,
`userConfirmPassword` VARCHAR(50) NOT NULL,
PRIMARY KEY (`userId`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=2
;
When I try to run my first query which is going to create authorities table, I am getting ERROR 1215: Cannot add foreign key constraint error.
So far, I've been looked into these questions below, but none of them answered my problem + I think they are both the same questions:
MySQL Cannot Add Foreign Key Constraint
MySQL Error 1215: Cannot add foreign key constraint
Try making userFirstName column unique.
You haven't mentioned what the engine is for authorities, your user table does not have a unique index on userFirstName that would mean authoritiest would need to be INNODB. because:
InnoDB allows a foreign key constraint to reference a non-unique key.
This is an InnoDB extension to standard SQL.
But I don't recommend this at all, it's always best to have a foreign key referencing a PRIMARY KEY, where that's not possible another unique key.
What you should really do is change your table as follows:
CREATE TABLE authorities (
userid INT(11) not null,
authority varchar(50) not null,
CONSTRAINT fk_authorities_users foreign key(username) references user(userid));
create unique index ix_auth_username on authorities (username,authority));
By doing so you are referencing a PRIMARY KEY but more importantly reducing a great deal of redundancy from your tables. There is absolutely no point in having the same ~70 character field repeated in both tables.

What does it change between this two way of declaring a foreign key?

Good morning, I'm studing the SQL, and today I've found two ways of declaring a foreign key (for MySQL). I'd like to know what does it change between that two syntax and why should I need to set a name for the foreign key (Syntax 2).
Syntax 1:
CREATE TABLE `test2` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`idtest` int(10) unsigned NOT NULL,
`desc` varchar(45) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`idtest`) REFERENCES `test` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Syntax 2:
CREATE TABLE `test2` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`idtest` int(10) unsigned NOT NULL,
`desc` varchar(45) NOT NULL,
PRIMARY KEY (`id`),
KEY `FK_1` (`idtest`),
CONSTRAINT `FK_1` FOREIGN KEY (`idtest`) REFERENCES `test` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Thank you!
Functionally there is no difference.
The first example does name the Foreign Key, but it's the RDBMS that does the naming.
The second example lets you expressly name the Foreign Key yourself.
The ability to name the Foreign Key yourself allows you to communicate to other developers what the key means, and conform to standard naming conventions, etc.
The second syntax enables you to delete, modify or reuse that constraint at some point in the future.
The first syntax can not be changed as it is in the definition of the table.
The optional CONSTRAINT keyword allows you to specify a name for the foreign key. Without it, a name will be generated automatically.
This name can be seen in the INFORMATION_SCHEMA TABLE_CONSTRAINTS table.