I have an approval tag and would like it to be populated down when it is changed in the parent table, however I cannot figure out how to reference the parent from the child to pull the value down.
For reference I want the value approval in reservations to be populated down to reservedTickets when I update the approval from 0 to 1 in reservations. However, to my understanding I cannot use a standard FOREIGN KEY or REFERENCE reference to reservations since approval is not unique.
Before anyone says anything about "why not have the value in only one table" it is to separate concerns between the administrator who has the ability to update the reservations table and a non-admin updating reservedTickets. Also, due to the high number of FOREIGN KEY constraints in reservedTickets having to join with the reservations table to track approval can be tricky depending on my starting point.
CREATE TABLE reservations(
rid int NOT NULL AUTO_INCREMENT,
aid int NOT NULL,
approval tinyint(1) NOT NULL,
creationTime TIMESTAMP
DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (rid),
FOREIGN KEY (aid) REFERENCES accounts (aid)
ON DELETE CASCADE
);
CREATE TABLE reservedTickets(
rid int NOT NULL,
tid int NOT NULL,
hid int NOT NULL,
approval tinyint(1) NOT NULL,
PRIMARY KEY (tid),
FOREIGN KEY (rid) REFERENCES reservations (rid)
ON DELETE CASCADE,
FOREIGN KEY (tid) REFERENCES tickets (tid)
ON DELETE CASCADE,
FOREIGN KEY (hid) REFERENCES people (hid)
ON DELETE CASCADE,
FOREIGN KEY (approval) REFERENCES reservations (approval)
ON UPDATE CASCADE
);
Personally I'd avoid cascade updates because of many locks the engine issues behind the scene, but if you really want it you can have a unique constraint on reservations(rid,approval) and foreign key in reservedTickets that references it. As far as I remember Mysql, it comes with a cost of extra index, but it gives you what you want.
On the other hand , you can implement desired functionality with trigger on reservation, or leave it up to application (or update table only through stored procedure that takes care of carrying this flag).
CREATE TABLE reservations(
rid int NOT NULL AUTO_INCREMENT,
aid int NOT NULL,
approval tinyint(1) NOT NULL,
creationTime TIMESTAMP
DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (rid),
CONSTRAINT UQ_RESERVATION_COMPOSITE UNIQUE(rid, approval),
...
);
CREATE TABLE reservedTickets(
rid int NOT NULL,
tid int NOT NULL,
hid int NOT NULL,
approval tinyint(1) NOT NULL,
PRIMARY KEY (tid),
FOREIGN KEY (rid,approval) REFERENCES reservations (rid,approval)
ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (tid) REFERENCES tickets (tid)
ON DELETE CASCADE,
FOREIGN KEY (hid) REFERENCES people (hid)
ON DELETE CASCADE , ....
);
It's not so much that you cannot use a foreign key on a non-unique column, because you can in InnoDB. However, the problem that arises with this would be if you have automatic updating or deleting on the foreign key, because the database would update all keys that were the same.
What you may be able to do, is use a prepared statement that allows you to update the approval column when updated from the reservations table, etc.
Related
CREATE TABLE doctor(
Did varchar(30) not null,
spid int,
Hid int,
Dname varchar(200)not null,
Dnumber int,
fee decimal(10,2)not null,
constraint primary key(Did),
constraint unique(Did,Dname),
constraint foreign key(spid)references speciality(spid)
on delete cascade on update cascade,
constraint foreign key(Hid)references hospital(Hid)
on delete cascade on update cascade
)engine=innodb;
It's hard to give an answer without the error message or your version of mysql, but I'd recommend to be sure that spid column of speciality is also defined is int, not int(11) or unsigned int(and also for other foreign key references).
And also your primary key is already always unique, I don't see any point including that in another unique constraint.
I have 3 tables which are linked to each other
task
client
compliance
The relationship is as shown in diagram below
The compliance table has the foreign key as below
The task table has the foreign key as below
Issue:
When I edit/update clientno in client table, I get
1452: Cannot add or update a child row: a foreign key constraint fails
(`task`, CONSTRAINT `task_ibfk_1` FOREIGN KEY (`officeid`, `clientid`) REFERENCES `client` (`officeid`, `clientno`) ON UPDATE CASCADE)
I expected that when clientno was changed in client table, the same will be updated in both complaince and task table.
I guess I am hitting a known limitation of InnoDB engine. Which goes not allow cascading updates to FK. If this is true, then what is the solution to updating the 3 tables with new clientno?
EDIT 1: As pointed out by #pankaj, how to overcome
If ON UPDATE CASCADE recurses to update the same table it has previously updated during the cascade, it acts like RESTRICT. This means that you cannot use self-referential ON UPDATE CASCADE operations. This is to prevent infinite loops resulting from cascaded updates.
Edit 2:
create table client
(
officeid char(6) not null,
clientno char(10) not null,
fname varchar(40) not null,
primary key (officeid, clientno)
);
create index officeid_clientno
on client (officeid, clientno);
create table compliance
(
officeid char(6) not null,
id smallint(5) unsigned not null,
clientid char(10) not null,
primary key (officeid, id),
constraint compliance_ibfk_2
foreign key (officeid, clientid) references client (officeid, clientno)
on update cascade
on delete cascade
);
create index officeid_clientid
on compliance (officeid, clientid, id);
create table task
(
officeid char(6) not null,
taskno char(10) not null,
clientid char(10) not null,
taskname varchar(50) not null,
complianceid smallint(5) unsigned null,
primary key (officeid, taskno),
constraint task_ibfk_1
foreign key (officeid, clientid) references client (officeid, clientno)
on update cascade,
constraint task_ibfk_4
foreign key (officeid, clientid, complianceid) references compliance (officeid, clientid, id)
on update cascade
);
create index officeid_clientid_complianceid
on task (officeid, clientid, complianceid);
FYI: I tried in mariadb 10.3 as well as mysql 8.0
The problem is related to the way relationships are declared.
First of all, as commented by #Nick, there is no need for a relation between task and client, as this is already covered by the relation to compliance. Commenting the declaration of this superfluous constraint is enough the make the error disappear, as you can see in this db fiddle.
create table task
(
officeid char(6) not null,
...
primary key (officeid, taskno),
-- constraint task_ibfk_1
-- foreign key (officeid, clientid) references client (officeid, clientno)
-- on update cascade,
constraint task_ibfk_4
foreign key (officeid, clientid, complianceid) references compliance (officeid, clientid, id)
on update cascade
);
Another suggestion is to use an autoincremented primary key in all tables (you can use an UNIQUE index to enforce composite referential integrity rules). This is the most usual way to proceed with MySQL, with which handling relationships is pretty straighforward.
I think that your problem stems from using mutable fields as primary keys
You can mitigate this by using a surrogate immutable primary key and adding a unique key to your mutable fields. You should be able to apply the same constraints as before without compromising data integrity
For example:
CREATE TABLE client (
id INT(10) UNSIGNED NOT NULL AUTO-INCREMENT PRIMARY,
officeid CHAR(6) NOT NULL,
clientno CHAR(10) NOT NULL,
fname VARCHAR(40) NOT NULL
);
CREATE UNIQUE INDEX uq-client-officeid-clientno IN client (officeid, clientno);
CREATE TABLE compliance (
id SMALLINT(5) UNSIGNED NOT NULL AUTO-INCREMENT PRIMARY,
client_id INT(10) UNSIGNED NOT NULL,
CONSTRAINT fk-compliance-client-id FOREIGN KEY id
REFERENCES client (id)
);
CREATE INDEX ix-compliance-id-client_id IN compliance (id, client_id);
CREATE TABLE task (
id INT(10) UNSIGNED NOT NULL AUTO-INCREMENT PRIMARY,
client_id INT(10) UNSIGNED NOT NULL,
compliance_id SMALLINT(5) UNSIGNED NULL,
taskno CHAR(10) NOT NULL,
taskname VARCHAR(50) NOT NULL,
CONSTRAINT fk-task-client-id FOREIGN KEY id
REFERENCES client (id),
CONSTRAINT fk-task-compliance-id-client_id FOREIGN KEY (compliance_id, client_id)
REFERENCES compliance (id, client_id)
);
This table structure mimics your current constraints and will allow you to update a clientno without needing the cascades
Note the foreign key fk-task-compliance-id-client_id which makes sure the compliance referenced by a task contains the correct client_id
I would also consider a separate table, office, with a surrogate integer primary key and containing the character based officeid. This could then be reference by the client table
I have a products table, and a product_variants table (one-to-many).
The product_variants table has the following structure:
CREATE TABLE product_variants (
id int(11) NOT NULL AUTO_INCREMENT,
id_product int(11) NOT NULL,
id_colourSet int(11) DEFAULT NULL,
id_size int(11) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY UNIQUE (id_product,id_colourSet,id_size),
KEY idx_prod (id_product),
KEY idx_colourSet (id_colourSet),
KEY idx_size (id_size),
CONSTRAINT fk_df_product_variants_id_colurSet FOREIGN KEY (id_colourSet) REFERENCES df_colour_sets (id_colourSet) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_df_product_variants_id_product FOREIGN KEY (id_product) REFERENCES df_products (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_df_product_variants_id_size FOREIGN KEY (id_size) REFERENCES df_sizes (id) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB
The options are known at compile-time. Each option is foreign-keyed to a dedicated table, and the unique key is the combination of all options.
I then insert products with an "ON DUPLICATE KEY UPDATE ..." statement, and if a variant already exists the query will use an existing variant.
The problem is that certain products do not have a color, nor a size. In this case the unique constraint fails and I insert lots of almost-empty rows in the product_variants table.
In order to solve this problem I am creating a "NULL" value for each option (e.g. "NO_COLOR", "NO_SIZE") in the respective option tables, and using that as the default value for the option columns in the product_variants table.
Would this be the recommended solution? Is there a better way of structuring this data? I would really like to avoid an EAV design.
Thank you
Designating a magic value that means "missing value" is not the right solution in almost every case. That's what NULL is for.
It's also not clear how "NO_COLOR" is used for an integer. I guess it would map to the value 0, which is typically not used in an auto-increment column.
You can create another column to be a hash of the three unique key columns, defaulted to '' to avoid null problems. Then put a unique constraint on that hash.
CREATE TABLE product_variants (
id int(11) NOT NULL AUTO_INCREMENT,
id_product int(11) NOT NULL,
id_colourSet int(11) DEFAULT NULL,
id_size int(11) DEFAULT NULL,
option_hash binary(16) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY (option_hash),
KEY idx_prod (id_product),
KEY idx_colourSet (id_colourSet),
KEY idx_size (id_size),
CONSTRAINT fk_df_product_variants_id_colurSet FOREIGN KEY (id_colourSet) REFERENCES df_colour_sets (id_colourSet) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_df_product_variants_id_product FOREIGN KEY (id_product) REFERENCES df_products (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_df_product_variants_id_size FOREIGN KEY (id_size) REFERENCES df_sizes (id) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB;
CREATE TRIGGER product_variants_ins BEFORE INSERT ON product_variants
FOR EACH ROW SET option_hash = UNHEX(MD5(CONCAT_WS('|',
COALESCE(id_product, ''),
COALESCE(id_colourSet, ''),
COALESCE(id_size, ''))));
CREATE TRIGGER product_variants_upd BEFORE UPDATE ON product_variants
FOR EACH ROW SET option_hash = UNHEX(MD5(CONCAT_WS('|',
COALESCE(id_product, ''),
COALESCE(id_colourSet, ''),
COALESCE(id_size, ''))));
I thought the point of ON DELETE CASCADE was that this wouldn't happen. :\ I have the following tables:
CREATE TABLE Tweets (
tweetID INTEGER NOT NULL AUTO_INCREMENT,
userID INTEGER NOT NULL,
content VARCHAR(140) NOT NULL,
dateTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
hasPoll INTEGER NOT NULL,
visible INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (tweetID),
FOREIGN KEY (userID) REFERENCES Users(userID)
ON DELETE CASCADE
);
CREATE TABLE Polls (
pollID INTEGER NOT NULL AUTO_INCREMENT,
tweetID INTEGER NOT NULL,
pollOptionText VARCHAR(300),
PRIMARY KEY (pollID),
FOREIGN KEY (tweetID) REFERENCES Tweets(tweetID)
);
The problem is that when I try to delete a Tweet which has a Poll attached to it, I get the following error (via Flask):
_mysql_exceptions.IntegrityError
IntegrityError: (1451, 'Cannot delete or update a parent row: a foreign key constraint fails (`twitter`.`polls`, CONSTRAINT `polls_ibfk_1` FOREIGN KEY (`tweetID`) REFERENCES `Tweets` (`tweetID`))')
Help please!
That is indeed the point of on delete cascade. You get the error, because your code doesn't declare on delete cascade from "poll" to "tweet".
CREATE TABLE Polls (
pollID INTEGER NOT NULL AUTO_INCREMENT,
tweetID INTEGER NOT NULL,
pollOptionText VARCHAR(300),
PRIMARY KEY (pollID),
FOREIGN KEY (tweetID) REFERENCES Tweets(tweetID)
ON DELETE CASCADE
);
This will delete rows in "Polls" when corresponding rows are deleted in "Tweets".
You have to put ON DELETE CASCADE after FOREIGN KEY (tweetID) REFERENCES Tweets(tweetID)
According to the MySQL Foreign Key Constraints reference:
CASCADE: Delete or update the row from the parent table, and
automatically delete or update the matching rows in the child table.
Both ON DELETE CASCADE and ON UPDATE CASCADE are supported.
Also, according to the MySQL Foreign Keys reference:
For storage engines other than InnoDB, it is possible when defining a
column to use a REFERENCES tbl_name(col_name) clause, which has no
actual effect, and serves only as a memo or comment to you that the
column which you are currently defining is intended to refer to a
column in another table.
So since the foreign key is from the child table to the parent table, it makes foo a parent table and Polls a child table, so deleting a row from Tweets will cascade deletions to Pools, providing you use InnoDB or some other storage engine that supports it.
UPDATE:
This error is because you have a relation between poll and twitter... without cascading you have to delete or update the polls removing the relation with the Tweet that will be deleted. Or use ON DELETE CASCADE:
CREATE TABLE Tweets (
tweetID INTEGER NOT NULL AUTO_INCREMENT,
content VARCHAR(140) NOT NULL,
PRIMARY KEY (tweetID)
);
CREATE TABLE Polls (
pollID INTEGER NOT NULL AUTO_INCREMENT,
tweetID INTEGER NOT NULL,
pollOptionText VARCHAR(300),
PRIMARY KEY (pollID),
FOREIGN KEY (tweetID) REFERENCES Tweets(tweetID)
ON DELETE CASCADE
);
INSERT INTO Tweets VALUES(1,'tweet');
INSERT INTO Polls VALUES(1,1,"pool");
DELETE FROM Tweets WHERE tweetID = 1;
You will surely get this error. you have to keep at least one record into tweets.
In database design, can 2 entities have 2 relationships among themselves? i.e for example there are 2 entities donor and admin.. there are 2 relationships
1. admin accesses donor details
2. admin can contact donor and vice versa
can we join them with 2 relationships?
Definitely, although how much sense it makes to model "accesses" and "contacts" relations in a database depends on your application. I'll stay with your example though and assume these relations are n to n. Here is how the SQL could look like (warning, syntax not tested):
CREATE TABLE admin (
id int unsigned AUTO_INCREMENT PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE donor (
id int unsigned AUTO_INCREMENT PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE admin_donor_access_details (
id_admin int unsigned NOT NULL,
id_donor int unsigned NOT NULL,
PRIMARY KEY (id_admin, id_donor),
CONSTRAINT FOREIGN KEY(id_admin) REFERENCES admin(id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT FOREIGN KEY(id_donor) REFERENCES donor(id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE admin_donor_contact (
id_admin int unsigned NOT NULL,
id_donor int unsigned NOT NULL,
PRIMARY KEY (id_admin, id_donor),
CONSTRAINT FOREIGN KEY(id_admin) REFERENCES admin(id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT FOREIGN KEY(id_donor) REFERENCES donor(id) ON DELETE CASCADE ON UPDATE CASCADE
);
The two relations could also be expressed in a single join table with boolean flags, like this:
CREATE TABLE admin_donor (
id_admin int unsigned NOT NULL,
id_donor int unsigned NOT NULL,
detail_access tinyint(1) NOT NULL,
contact tinyint(1) NOT NULL,
PRIMARY KEY (id_admin, id_donor),
CONSTRAINT FOREIGN KEY(id_admin) REFERENCES admin(id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT FOREIGN KEY(id_donor) REFERENCES donor(id) ON DELETE CASCADE ON UPDATE CASCADE
);
This will put some extra effort on your code because you need to determine whether to insert or update a row when adding a relationship, and whether to delete or update a row when removing a relationship, but in my opinion this is still a usable alternative.