Cascading deletes not working when deleting from a 'grouping table' - mysql

here is a stripped down example of my problem:
I create 2 tables, which are connected via a 'grouping table'.
CREATE TABLE table1
(
t1_pk INT(11) AUTO_INCREMENT NOT NULL,
t1_entry VARCHAR(150),
PRIMARY KEY (t1_pk)
) engine = innodb;
CREATE TABLE table2
(
t2_pk int(11) AUTO_INCREMENT NOT NULL,
t2_entry VARCHAR(150),
PRIMARY KEY (t2_pk)
) engine = innodb;
CREATE TABLE grouping
(
grouping_pk INT(11) AUTO_INCREMENT NOT NULL,
t1_fk INT(11) NOT NULL,
t2_fk INT(11) NOT NULL,
PRIMARY KEY (grouping_pk),
CONSTRAINT table1_fk FOREIGN KEY (t1_fk) REFERENCES table1 (t1_pk) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT table2_fk FOREIGN KEY (t2_fk) REFERENCES table2 (t2_pk) ON DELETE CASCADE ON UPDATE CASCADE
) engine = innodb;
Now I want to delete all the entries from grouping, table1 and table2 where table1.t1_entry is "abc".
I try to do it like this:
DELETE FROM grouping
WHERE grouping.grouping_pk IN (SELECT
temp.entry_id
FROM (SELECT grouping.grouping_pk,
grouping.t1_fk,
grouping.t2_fk,
table1.t1_pk,
table1.t1_entry,
table2.t2_pk,
table2.t2_entry
FROM grouping
LEFT OUTER JOIN table1 ON grouping.t1_fk = table1.t1_pk
LEFT OUTER JOIN table2 ON grouping.t2_fk = table2.t2_pk
WHERE table1.t1_entry LIKE 'abc'
) AS temp)
As a result, the entries are deleted in the grouping table, but not in table1 and table2.
My question is now, how could I selected records and delete the result set from all tables? I feel like a dummy, because I can't figure this out by myself.

In grouping table definition of table2_fk you references to table1 instead of table2. It may be that the problem...

Closing this one now...
Conclusion:
I have to rework my datamodel for a better solution to my problem, based on Barmar and Spencer7593 comments.
Thanks for the much appreciated help!

Related

How to add columns from one MySQL table to another without duplicates?

There are two tables. It is necessary to insert two fields from one to the other so that duplicates do not appear. I tried those methods that are described for
USING INSERT IGNORE
Using replace
USING INSERT ... on duplicate key update
But I didn't succeed. For example, it ignore duplicate and write these:
REPLACE INTO user_favorites
(user_id, partner_id)
SELECT id, partner_id FROM users
How to do it?
1 table
create table local.users
(
id int auto_increment,
name varchar(255) null,
email varchar(255) null,
password varchar(255) null,
partner_id int null,
constraint users_email_unique
unique (email),
constraint users_id_uindex
unique (id)
)
alter table local.users
add primary key (id);
2 table
create table local.user_favorites
(
id int auto_increment,
user_id int null,
partner_id int null,
constraint user_favorites_id_uindex
unique (id),
constraint user_favorites_partners_id_fk
foreign key (partner_id) references local.partners (id)
on update cascade on delete cascade,
constraint user_favorites_users_id_fk
foreign key (user_id) references local.users (id)
on update cascade on delete cascade
);
alter table local.user_favorites
add primary key (id);
insert ignore and insert ... on duplicate key and replace all detect duplicates by whatever unique key constraints you have. Right now, your only unique constraint in user_favorites is the primary key id, which obviously doesn't help.
Add a unique constraint on user_id and partner_id:
alter table local.user_favorites add unique (user_id,partner_id);
If that fails, you already have duplicates that you will need to clean up first.
Then do any of the things you tried to add the rows from users.
You can first add empty columns and then update corresponding values by join operation. Like this:
ALTER TABLE user_favorites ADD COLUMN
name VARCHAR(255) NULL,
email VARCHAR(255) NULL,
password VARCHAR(255) NULL;
CONSTRAINT users_email_unique UNIQUE(email);
UPDATE user_favorites tb1
INNER JOIN users tb2 ON tb1.user_id = tb2.id
AND tb1.partner_id = tb2.partner_id
SET tb1.name = tb2.name
tb1.email = tb2.email
tb1.password = tb2.password;
Reference here: https://www.tutorialspoint.com/can-we-add-a-column-to-a-table-from-another-table-in-mysql

Delete, upon foreign key constraint update

MySQL offers the following:
INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1;
Does MySQL offer something similar for DELETE which would attempt to delete and upon foreign key constraint, update the record in the table which was attempted to be deleted?
For instance...
DELETE FROM table1 WHERE idtable1 = 123;
IF(foreign key constraint) { //pseudo code...
UPDATE table1 SET deleted=1 WHERE idtable1 = 123;
}
CREATE TABLE IF NOT EXISTS table1 (
idtable1 INT NOT NULL,
data VARCHAR(45) NULL,
deleted TINYINT NOT NULL DEFAULT 0,
PRIMARY KEY (idtable1))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS table2 (
idtable2 INT NOT NULL,
table1_idtable1 INT NOT NULL,
data VARCHAR(45) NULL,
INDEX fk_table2_table1_idx (table1_idtable1 ASC),
CONSTRAINT fk_table2_table1
FOREIGN KEY (table1_idtable1)
REFERENCES table1 (idtable1)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
If I understand correctly, you want a cascading foreign constraint on delete and/or update:
CREATE TABLE IF NOT EXISTS table2 (
idtable2 INT NOT NULL,
table1_idtable1 INT NOT NULL,
data VARCHAR(45) NULL,
INDEX fk_table2_table1_idx (table1_idtable1 ASC),
CONSTRAINT fk_table2_table1
FOREIGN KEY (table1_idtable1)
REFERENCES table1 (idtable1)
ON DELETE CASCADE
ON UPDATE CASCADE
);
This will delete the row in table2 when the corresponding row in table1 is deleted.
You can read about the different types of foreign key constraints in the documentation.

foreign key constraint on update cascade on delete cascade

I have two tables: one and two. I have a primary key (id) in table one.
Table One:
CREATE TABLE `one` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`first_name` varchar(10) NOT NULL,
`last_name` varchar(10) NOT NULL,
`salary` int(100) NOT NULL,
`login_date_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1
id first_name last_name salary login_date_time
Table Two
CREATE TABLE two AS (SELECT id,salary ,CONCAT(first_name, ',', last_name) AS Name from one)
Now adding the foreign key to id as:
ALTER TABLE two
ADD CONSTRAINT `0_38775` FOREIGN KEY (id)
REFERENCES one(id) ON DELETE CASCADE ON UPDATE CASCADE
the table two comes as with the values from table one:
id salary name
Now to maintain referential integrity, how to update the values of table two when table one is updated. And also I cannot delete rows from table one and cannot insert rows to table two.
How can I update all the columns of table two when table one is updated/inserted. or insert explicitly into table two
Well, for the case of delete, when you delete the row from table 1, all the rows corresponding to that id will be deleted from table 2.
In case of update, if you want first name and last name to be updated in table 2 when they are altered in table 1 then you will have to write an update trigger for that.

Optimizing this MySQL Query

Table Schema
For the two tables, the CREATE queries are given below:
Table1: (file_path_key, dir_path_key)
create table Table1(
file_path_key varchar(500),
dir_path_key varchar(500),
primary key(file_path_key))
engine = innodb;
Table2: (file_path_key, hash_key)
create table Table2(
file_path_key varchar(500) not null,
hash_key bigint(20) not null,
foreign key (file_path_key) references Table1(file_path_key) on update cascade on delete cascade)
engine = innodb;
Objective:
Given a file_path F and it's dir_path string D, I need to find all those
file names which have at least one hash in the set of hashes of F, but
don't have their directory names as D. If a file F1 shares multiple hashes
with F, then it should be repeated that many times.
Note that the file_path_key column in Table1 and the hash_key column in Table2 are indexed.
In this particular case, Table1 has around 350,000 entries and Table2 has 31,167,119 entries, which makes my current query slow:
create table temp
as select hash_key from Table2
where file_path_key = F;
select s1.file_path_key
from Table1 as s1
join Table2 as s2
on s1.file_path_key join
temp on temp.hash_key = s2.hash_key
where s1.dir_path_key != D
How can I speed up this query?
I do not understand what is the purpose of temp table, but remember that such table, created with CREATE .. SELECT, does not have any indexes. So at the very least fix that statement to
CREATE TABLE temp (INDEX(hash_key)) ENGINE=InnoDB AS
SELECT hash_key FROM Table2 WHERE file_path_key = F;
Otherwise the other SELECT performs full join with temp, so it might be very slow.
I would also suggest using a numerical primary key (INT, BIGINT) in Table1 and reference it from Table2 rather than the text column. Eg:
create table Table1(
id int not null auto_increment primary key,
file_path_key varchar(500),
dir_path_key varchar(500),
unique key(file_path_key))
engine = innodb;
create table Table2(
file_id int not null,
hash_key bigint(20) not null,
foreign key (file_id) references Table1(id)
on update cascade on delete cascade) engine = innodb;
Queries joining the two tables may be a lot faster if integer columns are used in join predicate rather than text ones.

multiple row update on related table

I would like to update multiple rows of column 'student_total' in Table 'Teacher' when a student is deleted, using triggers/procedures
Updating multiple rows in related tables, many-to-many realtionships
'n' teachers can have 'm' students
is it possible at all ? because its not possible to store a
result set (learnt from your site)
in Mysql, Postgress etc?
Thanks in Adv
Ritin
--------------- SQL
CREATE TABLE `Teacher` (
`id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`student_total` int(11) DEFAULT '0',
PRIMARY KEY (`id`)
);
CREATE TABLE `Student` (
`id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `Teacher_has_Student` (
`teacher_id` smallint(5) unsigned NOT NULL,
`student_id` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`teacher_id`,`student_id`),
KEY `fk_Teacher_has_Student_teacher` (`teacher_id`),
KEY `fk_breeder_has_breed_student` (`student_id`),
CONSTRAINT `fk_Teacher_has_Student_teacher` FOREIGN KEY (`teacher_id`) REFERENCES `Teacher` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_breeder_has_breed_student` FOREIGN KEY (`student_id`) REFERENCES `Student` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
i have triggers for INSERT/DELETE/UPDATE
ex:
---------------------- SQL
CREATE TRIGGER teacher__student_insert AFTER INSERT ON Teacher_has_Student
FOR EACH ROW
BEGIN
UPDATE Teacher SET student_total = student_total + 1 WHERE id = NEW.teacher_id;
END;
The trigger below updates just 1 row, whereas the aim is to update all the rows.
DELIMITER |
CREATE TRIGGER my_student__delete AFTER DELETE ON Student
FOR EACH ROW
BEGIN
set #std_id = old.id;
UPDATE teacher SET student_total = student_total - 1
WHERE id = #std_id;
END
|
I think your 2nd trigger doesn't make sense. On a delete of a student you take his id and decrease the student_total of the teacher, which has accidentally the same id as the as the studend.
Your definition plus the first trigger should already do the job. If you delete a student his relations are deleted from Teacher_has_Student by the CASCADE and on this DELETE your triggers should be fired to decrease the student_total of the affected teachers. Anyway a short Google research shows, that MySQL apparently doesn't fire triggers on a cascaded delete:
http://bugs.mysql.com/bug.php?id=13102
Possible Workarounds
You could try to fix your 2nd trigger to do the job manually:
DELIMITER |
CREATE TRIGGER my_student__delete AFTER DELETE ON Student
FOR EACH ROW
BEGIN
SET #std_id = old.`id`;
UPDATE
`Teacher` AS T
INNER JOIN `Teacher_has_Student` AS TS ON T.`id`=TS.`teacher_id`
SET `student_total` = `student_total` - 1
WHERE ST.`student_id` = #std_id;
END
|
But this will not work, if the CASCADE on the FOREIGN KEY is executed before your trigger.
Maybe a more reliable workaround is to drop the FOREIGN KEY definitions and to create triggers on Teacher and Student to cascade the deletes and updates to Teacher_has_Student.
Normalize it
With the workarounds you have to keep a lot of different scenarios in mind to keep student_total valid. If it‘s not absolutely necessary to store student_total directly at the Teachers-Table, I suggest strongly not to do it.
The information is absolutely redundant. Keep your constraints and cascades, but drop the student_total column and delete all triggers. Then use a JOIN to get your student numbers:
SELECT
T.`id`, T.`name`, COUNT(*) AS student_total
FROM `Teacher` AS T
INNER JOIN `Teacher_has_Student` AS TS ON T.`id`=TS.`teacher_id`
GROUP BY T.`id`