MySQL FK Cascade - not seeing my mistake - mysql

I have read through countless threads on this and am still missing something.
When I delete a row from either table no constraint or cascade occurs.
My goal is to cascade delete any child rows.
CREATE SCHEMA IF NOT EXISTS `my_schema`
DEFAULT CHARACTER SET latin1;
USE `my_schema`;
SET foreign_key_checks = 0;
DROP TABLE IF EXISTS `test_types`;
DROP TABLE IF EXISTS `test_core_types`;
SET foreign_key_checks = 1;
-- -----------------------------------------------------
-- Table `test_core_types`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `test_core_types` (
`test_core_type_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`data` VARCHAR(100) NOT NULL,
PRIMARY KEY (`test_core_type_id`))
ENGINE = InnoDB
AUTO_INCREMENT = 1;
CREATE UNIQUE INDEX `test_core_types__data_UNIQUE` ON `test_core_types` (`data` ASC);
-- -----------------------------------------------------
-- Table `test_types`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `test_types` (
`test_type_id` SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
`test_core_type_id` INT(10) UNSIGNED NOT NULL,
`name` VARCHAR(45) NOT NULL,
`is_viewable` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1',
PRIMARY KEY (`test_type_id`))
ENGINE = InnoDB
AUTO_INCREMENT = 1;
CREATE UNIQUE INDEX `test_types__name_UNIQUE` ON `test_types` (`name` ASC);
CREATE INDEX `idx_test_core_type_00` ON `test_types` (`test_core_type_id` ASC);
ALTER TABLE `test_types`
ADD CONSTRAINT `fk_test_core_type_00`
FOREIGN KEY (`test_core_type_id`)
REFERENCES `test_core_types` (`test_core_type_id`)
ON DELETE CASCADE
ON UPDATE NO ACTION;
Insert:
INSERT INTO `test_core_types` (`test_core_type_id`, `data`) VALUES ( 1, 'General');
INSERT INTO `test_types` (`test_type_id`, `test_core_type_id`, `name`, `is_viewable`) VALUES ( 1, 1, 'My General Item', 1);
Delete:
DELETE FROM `test_core_types` WHERE `test_core_type_id` = 1;
Result:
SELECT * FROM `test_core_types`;
/* --> Row Deleted */
SELECT * FROM `test_types`;
/* --> Row still exists */
When I perform a delete the row is deleted without errors.
Does not matter which table.
Any help is greatly appreciated.

Theory:
For your child table (test_types) you have mentioned ON DELETE CASCADE; which means when you delete a row from parent table (test_core_types); same delete will get cascaded to child table (S) as well and corresponding related row from child table will be deleted.
After your delete query; if you try a select from both table you will find 0 rows present. That's the actual behavior of ON DELETE CASCADE.
For Real example check the below mentioned fiddle link
http://sqlfiddle.com/#!2/33014e/1

You've specified a DELETE rule of CASCADE.
We'd expect a delete from the parent table would succeed, related rows in the table with the foreign key reference will also be deleted. We wouldn't expect any error.
If you want the DELETE operation in your code to throw an error, then define the foreign key reference with a DELETE rule of RESTRICT.
I recommend you verify that FOREIGN_KEY_CHECKS is enabled, and that the foreign key constraint is actually defined (i.e. that the ALTER TABLE statement that added the constraint succeeded. I've never tried specifying NO ACTION as an update rule; I know that's the default when no rule is specified, but I always specify either RESTRICT, CASCADE or SET NULL.) Also verify that the table is actually using the InnoDB storage engine.
SHOW VARIABLES LIKE '%foreign_key_checks%' ;
SHOW CREATE TABLE `test_types` ;

Related

Deleting a row with a foreign key constraint with a Before Delete TRIGGER in MySql

I have a table called 'estoque' with a foreign key that references another table called 'produto'. Then I've populated both tables with a few rows.
Here are my tables:
CREATE TABLE `produto` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`NOME` varchar(45) NOT NULL,
`PRECO` float NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `NOME_UNIQUE` (`NOME`)
);
CREATE TABLE `estoque` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`ID_PRODUTO` int(11) NOT NULL,
`QUANTIDADE_PRODUTO` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`),
KEY `fk_Estoque_Produto1_idx` (`ID_PRODUTO`),
CONSTRAINT `fk_Estoque_Produto1` FOREIGN KEY (`ID_PRODUTO`)
REFERENCES `produto` (`ID`)
);
I need 'estoque' to always reference all existing rows on 'product'. So I'e created an AFTER INSERT and an AFTER UPDATE triggers on product:
CREATE TRIGGER `cadastrar_novo_produto_no_estoque`
AFTER INSERT ON `produto`
FOR EACH ROW
INSERT IGNORE INTO estoque (ID_PRODUTO)
VALUES (NEW.ID);
EDIT: Actually, since I can't alter the 'ID' column on 'produto' because it is a primary key, I've think I don't need the AFTER UPDATE trigger at all. Am I right?
CREATE TRIGGER `atualizar_novo_produto_no_estoque`
AFTER UPDATE ON `produto`
FOR EACH ROW
UPDATE estoque
SET estoque.ID_PRODUTO = NEW.ID
WHERE OLD.estoque.ID_PRODUTO = OLD.ID;
Now I need a trigger so that every time I delete a row from 'product', it also deletes the corresponding row in 'estoque'.
I've tried creating one like this:
CREATE TRIGGER `deletar_produto_inexistente_no_estoque`
BEFORE DELETE ON `produto`
FOR EACH ROW DELETE FROM estoque
WHERE estoque.ID_PRODUTO = ID;
But whenever I try to delete a row from 'produto' I get the following error:
ERROR 1175: 1175: You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column.
SQL Statement:
DELETE FROM `papelaria`.`produto` WHERE (`ID` = '6')
So I've tried it with the OLD keyword, as such:
CREATE TRIGGER `deletar_produto_inexistente_no_estoque`
BEFORE DELETE ON `produto`
FOR EACH ROW DELETE FROM estoque
WHERE OLD.estoque.ID_PRODUTO = OLD.ID;
And then I get this error instead:
ERROR 1054: 1054: Unknown column 'OLD.estoque.ID_PRODUTO' in 'where clause'
SQL Statement:
DELETE FROM `papelaria`.`produto` WHERE (`ID` = '6')
What am I missing or doing wrong?
P.S.: Not sure if it's worth mentioning but I'm fairly new to sql and programming in general, so I'd appreciate it if you took it into consideration when answering (for all purposes, just assume I don't know anything about anything ^^)
Thank you in advance!
You can modify your FK to:
CONSTRAINT `fk_Estoque_Produto1`
FOREIGN KEY (`ID_PRODUTO`)
REFERENCES `produto` (`ID`)
ON DELETE CASCADE
Then you will not need the before delete trigger any longer as records in estoque table will be deleted automatically.
Similar, adding ON UPDATE CASCADE will solve your 'update issue' in case someone update record's PK.
Still not sure why you'd like to have the dummy records in the estoque table.

Enforcing single subtype SQL table for supertype table

I have a supertype table called "vehicles". I also have three subtype tables called "airplanes", "automobiles", and "bicycles", and one and only one of these subtype tables must be linked to the vehicles supertype table (or in other words, must use the vehicles primary key ID as its primary key ID).
How should this be modeled to enforce this behavior?
EDIT Proposed schema recommended by Mike Brant.
-- MySQL Script generated by MySQL Workbench
-- 05/25/16 09:20:17
-- Model: New Model Version: 1.0
SET #OLD_UNIQUE_CHECKS=##UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET #OLD_FOREIGN_KEY_CHECKS=##FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET #OLD_SQL_MODE=##SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';
-- -----------------------------------------------------
-- Schema mydb
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `mydb` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;
USE `mydb` ;
-- -----------------------------------------------------
-- Table `mydb`.`vehicle_types`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`vehicle_types` (
`type` CHAR(8) NOT NULL,
PRIMARY KEY (`type`))
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `mydb`.`vehicles`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`vehicles` (
`idvehicles` INT NOT NULL AUTO_INCREMENT,
`type` CHAR(8) NOT NULL,
`data` VARCHAR(45) NULL,
PRIMARY KEY (`idvehicles`),
INDEX `fk_vehicles_vehicle_types_idx` (`type` ASC),
CONSTRAINT `fk_vehicles_vehicle_types`
FOREIGN KEY (`type`)
REFERENCES `mydb`.`vehicle_types` (`type`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `mydb`.`airplanes`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`airplanes` (
`vehicles_idvehicles` INT NOT NULL,
`data_for_airplanes` VARCHAR(45) NULL,
PRIMARY KEY (`vehicles_idvehicles`),
CONSTRAINT `fk_airplanes_vehicles1`
FOREIGN KEY (`vehicles_idvehicles`)
REFERENCES `mydb`.`vehicles` (`idvehicles`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `mydb`.`automobiles`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`automobiles` (
`vehicles_idvehicles` INT NOT NULL,
`data_for_automobiles` VARCHAR(45) NULL,
PRIMARY KEY (`vehicles_idvehicles`),
CONSTRAINT `fk_automobiles_vehicles1`
FOREIGN KEY (`vehicles_idvehicles`)
REFERENCES `mydb`.`vehicles` (`idvehicles`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `mydb`.`bicycles`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`bicycles` (
`vehicles_idvehicles` INT NOT NULL,
`data_for_bicycles` VARCHAR(45) NULL,
PRIMARY KEY (`vehicles_idvehicles`),
CONSTRAINT `fk_bicycles_vehicles1`
FOREIGN KEY (`vehicles_idvehicles`)
REFERENCES `mydb`.`vehicles` (`idvehicles`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
SET SQL_MODE=#OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=#OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=#OLD_UNIQUE_CHECKS;
I think inserting in vehicles and any of other sub types should be simultaneously. Therefore you can get the vehicle ID and put as new sub-type PK ID. If you have all information at the same time you can create a transcation and insert the vehicle record first then the sub-type record. Then commit your transscation otherwise rollback it. IF you want to know which record belongs to which sub-type table record you can add a column to your vehicle table that for example can have 3 different values such as 0,1,2 that shows which record belongs to which sub-table. Enum in java can be used in something like that for more clarification
Looking at your proposed schema, I see no reason whatsoever for you to have separate tables for each vehicle type, or even a separate table to contain the allowable vehicle types. Your vehicle-specific tables are all basically the same thing meaning you can easily collapse into a single table, and you can use ENUM field to enforce allowable vehicle types.
Why not just have a single vehicles table like the following?
CREATE TABLE IF NOT EXISTS `mydb`.`vehicles` (
`idvehicles` INT NOT NULL AUTO_INCREMENT,
`type` ENUM('airplane', 'automobile', 'bicycle') NOT NULL,
`data` VARCHAR(45) NULL,
PRIMARY KEY (`idvehicles`),
INDEX `fk_vehicles_vehicle_types_idx` (`type` ASC))
ENGINE = InnoDB;
This approach totally eliminates 4 of your 5 tables, meaning you no longer have to consider using joins, foreign key constraints, etc. when performing CRUD operations against these records.
To enforce exclusive subtypes, copy the type indicator into each of the subtype tables and use composite foreign key constraints:
CREATE TABLE IF NOT EXISTS `mydb`.`airplanes` (
`vehicles_idvehicles` INT NOT NULL,
`type` CHAR(8) NOT NULL,
`data_for_airplanes` VARCHAR(45) NULL,
PRIMARY KEY (`vehicles_idvehicles`),
CONSTRAINT `fk_airplanes_vehicles1`
FOREIGN KEY (`vehicles_idvehicles`, `type`)
REFERENCES `mydb`.`vehicles` (`idvehicles`, `type`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
Next you need to restrict the value of type in each table. Unfortunately, MySQL doesn't support check constraints, so you would need to use triggers:
DELIMITER ;;
CREATE TRIGGER airplanes_insert_type_check
BEFORE INSERT ON airplanes
FOR EACH ROW
BEGIN
IF NEW.`type` != 'airplane' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid type in airplanes';
END IF;
END;;
CREATE TRIGGER airplanes_update_type_check
BEFORE UPDATE ON airplanes
FOR EACH ROW
BEGIN
IF NEW.`type` != 'airplane' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid type in airplanes';
END IF;
END;;
DELIMITER ;
Thus, the type indicator in the supertype table will match only one of the subtype tables' type indicators due to the trigger restrictions, and will be enforced via foreign key constraint, preventing overlapping subtypes.

Deleting table with foreign key assigned

I'm trying to TRUNCATE a table called user_info, however I can't since in my other table user_profile I have a foreign key called f_key which is stopping me from truncating the user_info table. I tried to drop the foreign key but without success.
I tried:
ALTER TABLE user_profile DROP f_key
and:
ALTER TABLE user_profile DROP FOREIGN KEY f_key
but neither are working. Any ideas?
Output as suggested from comment below:
'user_profile', 'CREATE TABLE user_profile (
user_id int(11) NOT NULL AUTO_INCREMENT,
name varchar(30) COLLATE utf8_unicode_ci NOT NULL,
f_key int(11) NOT NULL,
PRIMARY KEY (user_id),
KEY f_key (f_key),
CONSTRAINT user_profile_ibfk_1 FOREIGN KEY (f_key) REFERENCES user_info (id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci'
You cannot use TRUNCATE for a foreign key referenced table. Imagine TRUNCATE as a combination of DROP TABLE and CREATE TABLE. Your table definition already has ON DELETE CASCADE which means a deleted master key will result in deletion of the child rows. However, TRUNCATE will skip those constraints.
Use DELETE FROM user_info instead.
You can also do this:
mysql> SET FOREIGN_KEY_CHECKS=0;
mysql> TRUNCATE TABLE user_info;
mysql> SET FOREIGN_KEY_CHECKS=1;
However, if any rows exist in user_profile that depended on the rows in user_info, then those rows would now be "orphaned".

MySQL Trigger : Check if primary key ID is in use in other table

I would like to create a trigger for my table table_master.
The table schema of table_master is simple:
master_id INT(11) AUTO_INCREMENT, PRIMARY, NOT NULL
title VARCHAR(50) NOT NULL
And here is another relation table rel_master_another_tbl
master_id INT(11) PRIMARY, NOT NULL
another_id INT(11) PRIMARY, NOT NULL
What I want to achieve is, when a DELETE query is issued on table_master, the trigger will check whether the master_id is used in rel_master_another_tbl . UPDATE: If yes, rollback / cancel the DELETE query.
How can I achieve this?
CREATE TRIGGER check_before_delete BEFORE DELETE ON table_master
// what should I put here?
END;
The behavior you are trying to implement already exists in the database and can be utilized via an ON DELETE RESTRICT trigger. An example of defining your tables to take advantage of this is shown below:
CREATE TABLE `master` (
`master_id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(50) DEFAULT NULL,
PRIMARY KEY (`master_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `rel_master_another_tbl` (
`master_id` int(11) NOT NULL,
`another_id` int(11) NOT NULL,
KEY `i_master_id` (`master_id`),
FOREIGN KEY `fk_rel_master_another_tbl_master` (`master_id`)
REFERENCES `master` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
RESTRICT in this context means that attempts to delete rows from your master table will fail, if there are corresponding rows in your rel_master_another_tbl that are referring to masters id column. Also, do note that RESTRICT is the default option for both ON DELETE and ON UPDATE triggers for foreign keys, so you could actually define your foreign key as shown below and it would still function the same:
FOREIGN KEY `fk_rel_master_another_tbl_master` (`master_id`)
REFERENCES `master` (`id`)
Refer to the MySQL documentation on foreign key constraints for more information.
If you want to check if this id exists in another table then you can do that this way:
DELIMITER $$
CREATE TRIGGER check_before_delete BEFORE DELETE ON table_master
FOR EACH ROW
BEGIN
DECLARE has_row TINYINT;
SELECT 1
INTO has_row
FROM rel_master_another_tbl
WHERE master_id = OLD.master_id;
IF has_row IS NOT NULL THEN
// PUT YOUR CODE HERE
END IF;
END$$
However check Perception's comment about setting key constrain between tables and using ON DELETE RESTRICT trigger as this could be proper solution for your problem.

mysql drop table and cascade delete to all references to the table

I'm developing a new system from an old system. The new system is using MySQL and java. I want to start with a reduced number of tables. When I delete a table lets say X, how can I cause all references to X to be deleted as well, so if table Y has an FK to table X then on table Y the FK and the column used in the FK get deleted as well?
simplified example:
CREATE TABLE `Y` (
`yID` int(11) NOT NULL AUTO_INCREMENT,
`yName` varchar(50) NOT NULL,
...
) ENGINE=InnoDB;
CREATE TABLE `user` (
`userID` int(11) NOT NULL AUTO_INCREMENT,
`userName` varchar(50) NOT NULL,
`givenName` varchar(50) DEFAULT NULL,
`sourceYID` int(11) NOT NULL,
CONSTRAINT `USER_FK_sourceYID` FOREIGN KEY (`sourceYID`) REFERENCES `Y` (`yID`)
) ENGINE=InnoDB;
I would like to preferably issue one command that will
DROP TABLE `Y`
and on the user table
remove the CONSTRAINT USER_FK_sourceYID
remove the column sourceYID
remove any KEY/INDEX definitions based on sourceYID as well if included (not included in this example)
There is no single command that can do this. The simplest way to handle this is to drop the constraint and then drop the parent table. Without the constraint, you can do this freely.
ALTER TABLE `user` DROP FOREIGN KEY `USER_FK_sourceYID`;
DROP TABLE `Y`;
Dropping the column automatically removes it from any indexes it belongs to. Even if it's a compound index, it leaves an index with the remaining columns. Here are some hypothetical example indexes, and we'll see what happens when we remove the column:
CREATE INDEX y1 ON `user` (sourceYID);
CREATE INDEX y2 ON `user` (userID, sourceYID);
CREATE INDEX y3 ON `user` (sourceYID, userID);
ALTER TABLE `user` DROP COLUMN `sourceYID`;
The result is that index y1 is gone, and both y2 and y3 are reduced to single-column indexes containing just the userID column:
SHOW CREATE TABLE `user`\G
CREATE TABLE `user` (
`userID` int(11) NOT NULL AUTO_INCREMENT,
`userName` varchar(50) NOT NULL,
`givenName` varchar(50) DEFAULT NULL,
PRIMARY KEY (`userID`),
KEY `y2` (`userID`),
KEY `y3` (`userID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
Therefore these two are now identical indexes, and you should run pt-duplicate-key-checker to analyze your schema for such cases.
SET FOREIGN_KEY_CHECKS = 0;
drop table if exists <your_1st_table>;
drop table if exists <your_2nd_table>;
SET FOREIGN_KEY_CHECKS = 1;
If you have a foreign key, then you wan't be able to remove parent table, the foreign key relation won't allow it. To do this you should do these steps:
remove all children records, there are two ways: using ON DELETE CASCADE foreign key option, or using multi-table DELETE statement.
remove foreign key if it exists.
drop parent table.