How to do this with foreign keys? - mysql

I have a table called categories, with the following fields:
id (primary key, identifies the categories)
user_id (foreign key, users.id, the creator of the current category)
category_id (foreign key, categories.id, the id of the parent category id)
Inside this table I have some records that are accessible to everyone.
For these records, both the user_id and the category_id fields are NULL.
In addition, users can create their own records (where the user_id field is not NULL), but every user can access only those that he has made himself.
Each record must meet these conditions:
user_id values must be valid
if the category_id is given then it must be a valid categories.id and that record must be created by the given user
the value of the category_id can be a categoies.id where the user_id is NULL
How can I do this? I think I need more foreign keys for this, but not sure how to do it.
Some examples that may help you to understand my problem:
Lets say I have the following records inside the categories table:
+--------+-------------+-----------------+
| id | user_id | category_id |
+--------+-------------+-----------------+
| 1 | NULL | NULL |
+--------+-------------+-----------------+
| 2 | NULL | NULL |
+--------+-------------+-----------------+
| 3 | 1 | 1 |
+--------+-------------+-----------------+
| 4 | 2 | 1 |
+--------+-------------+-----------------+
and lets say that I want to insert these records:
+-------------+-----------------+----------------------------------------------------------------+
| user_id | category_id | is it insertable?
+-------------+-----------------+----------------------------------------------------------------+
| 1 | NULL | yes, because the value of the user_id is valid id |
+-------------+-----------------+----------------------------------------------------------------+
| 1 | 1 | yes, because the record with id of 1 is created by NULL |
+-------------+-----------------+----------------------------------------------------------------+
| 1 | 3 | yes, because the record with id of 3 is created by user #1 |
+-------------+-----------------+----------------------------------------------------------------+
| 1 | 4 | no, because the record with id of 4 is created by another user |
+-------------+-----------------+----------------------------------------------------------------+
Categories table:
CREATE TABLE `categories` (
`id` int(11) NOT NULL,
`user_id` int(11) DEFAULT NULL,
`category_id` int(11) DEFAULT NULL,
`name` varchar(150) NOT NULL,
`type` enum('income','expense') NOT NULL DEFAULT 'income',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `categories`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `category_id` (`category_id`,`user_id`,`name`),
ADD KEY `user_id` (`user_id`);
ALTER TABLE `categories`
ADD CONSTRAINT `categories_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `categories_ibfk_2` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

Unfortunately, MySQL foreign keys can go so far. This is a bit of advanced logic. Might I recommend using a MySQL trigger?
DELIMITER $$
CREATE PROCEDURE `check_categories_user_id`(IN p_category_id INT(11), IN p_user_id INT(11))
BEGIN
IF (p_category_id IS NOT NULL) THEN
SET #other_user_id = (SELECT user_id
FROM categories
WHERE id = p_category_id);
IF (p_user_id <> #other_user_id) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'check constraint on categories.user_id failed';
END IF;
END IF;
END$$
CREATE TRIGGER `categories_before_update` BEFORE UPDATE ON `categories`
FOR EACH ROW
BEGIN
CALL check_categories_user_id(new.category_id, new.user_id);
END$$
CREATE TRIGGER `categories_before_insert` BEFORE INSERT ON `categories`
FOR EACH ROW
BEGIN
CALL check_categories_user_id(new.category_id, new.user_id);
END$$
DELIMITER ;
See dbfiddle here. (If you remove the last INSERT query, the SELECT query works as expected)
Additionally, to avoid some overhead (The SELECT query in the trigger), you can just enforce NULL on user_id when category_id IS NOT NULL (Using triggers like above). If category_id is NOT NULL, you'll just fetch the user_id from the parent row (Or the root row (parent's parent row...etc), if nested).

Related

Delete From in delete after trigger not deleting from another table

I am trying to make a DELETE TRIGGER so that when a row is deleted from a table Bike it will delete a row in another table called Available as long as it is not in a different table called in Rental. If it is in Rental table then it will cancel the delete (if that's possible I'm very new to sql).
BEGIN
DELETE FROM Available
Where old.bnumber = Available.bnumber;
END
right now I am getting
1451: Cannot delete or update a parent row: a foreign key constraint
fails (cecs535project.available, CONSTRAINT bnumber FOREIGN KEY
(bnumber) REFERENCES bike (bnumber))
bnumber is a foreign key in Available that references Bike.
Any help is appreciated.
CREATE TABLE `Bike` (
`bnumber` int NOT NULL,
`make` varchar(64) DEFAULT NULL,
`color` varchar(8) DEFAULT NULL,
`year` int DEFAULT NULL,
PRIMARY KEY (`bnumber`)
)
CREATE TABLE `Available` (
`bnumber` int NOT NULL,
`rack-id` int DEFAULT NULL,
PRIMARY KEY (`bnumber`),
KEY `bnumber_idx` (`rack-id`),
KEY `bnumber_idx1` (`bnumber`),
CONSTRAINT `bnumber` FOREIGN KEY (`bnumber`) REFERENCES `Bike` (`bnumber`),
CONSTRAINT `rack-id` FOREIGN KEY (`rack-id`) REFERENCES `Rack` (`id`)
)
CREATE TABLE `Rental` (
`date` date NOT NULL,
`time` time NOT NULL,
`bnumber` int NOT NULL,
`cust-id` int NOT NULL,
`src` int DEFAULT NULL,
PRIMARY KEY (`bnumber`,`cust-id`,`date`,`time`),
KEY `bnumber_idx` (`bnumber`),
KEY `cust-id_idx` (`cust-id`),
KEY `src_idx` (`src`),
CONSTRAINT `bike` FOREIGN KEY (`bnumber`) REFERENCES `Bike` (`bnumber`),
CONSTRAINT `cust-id` FOREIGN KEY (`cust-id`) REFERENCES `Customer` (`id`),
CONSTRAINT `src` FOREIGN KEY (`src`) REFERENCES `Rack` (`id`)
)
I am not a big fan of your current design, and I think it can be simplified. Consider just having a single table for all bike assets, with one column maintaining whether or not it be currently rented out, e.g.
Bike
id | name | type | rented (bit)
1 | bike1 | road | 1
2 | bike2 | mountain | 0
...
Now to record a bike being rented or not, you simply have to update the rented bit column above. Should you want to delete from your inventory, refraining from doing so if the bike be on loan, you can use:
DELETE
FROM Bike
WHERE rented = 0; -- AND your other conditions here
2 possible approaches
Use signal to identify where rental exists an throw an error https://dev.mysql.com/doc/refman/8.0/en/signal.html
test for rental existence in delete;
In both a before trigger is used since the constraint test occurs before an after trigger fires.
DROP TABLE IF EXISTS AVAILABLE;
drop table if exists rental;
drop table if exists BIKE;
CREATE TABLE `Bike` (
`bnumber` int NOT NULL,
`make` varchar(64) DEFAULT NULL,
`color` varchar(8) DEFAULT NULL,
`year` int DEFAULT NULL,
PRIMARY KEY (`bnumber`)
);
insert into bike values
(10,'aaa','red',2020),(20,'bbb','yell',2020);
CREATE TABLE `Available` (
`bnumber` int NOT NULL,
`rack-id` int DEFAULT NULL,
PRIMARY KEY (`bnumber`),
KEY `bnumber_idx` (`rack-id`),
KEY `bnumber_idx1` (`bnumber`),
CONSTRAINT `bnumber` FOREIGN KEY (`bnumber`) REFERENCES `Bike` (`bnumber`)#,
#CONSTRAINT `rack-id` FOREIGN KEY (`rack-id`) REFERENCES `Rack` (`id`)
) ;
insert into available values
(10,100),(20,200);
CREATE TABLE `Rental` (
#`date` date NOT NULL,
#`time` time NOT NULL,
`bnumber` int NOT NULL,
#`cust-id` int NOT NULL,
#`src` int DEFAULT NULL,
#PRIMARY KEY (`bnumber`,`cust-id`,`date`,`time`),
KEY `bnumber_idx` (`bnumber`),
#KEY `cust-id_idx` (`cust-id`),
#KEY `src_idx` (`src`),
CONSTRAINT `bike` FOREIGN KEY (`bnumber`) REFERENCES `Bike` (`bnumber`)#,
#CONSTRAINT `cust-id` FOREIGN KEY (`cust-id`) REFERENCES `Customer` (`id`),
#CONSTRAINT `src` FOREIGN KEY (`src`) REFERENCES `Rack` (`id`)
) ;
insert into rental values
(10);
Approach 1
drop trigger if exists t;
delimiter $$
create trigger t before delete on bike
for each row
begin
if exists (select 1 from rental r where r.bnumber = old.bnumber) then
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'An error occurred Rental exists';
end if;
delete a from available a where a.bnumber = old.bnumber;
end $$
delimiter ;
delete b from bike b;
ERROR 1644 (45000): An error occurred Rental exists
+---------+------+-------+------+
| bnumber | make | color | year |
+---------+------+-------+------+
| 10 | aaa | red | 2020 |
| 20 | bbb | yell | 2020 |
+---------+------+-------+------+
2 rows in set (0.001 sec)
select * from available;
+---------+---------+
| bnumber | rack-id |
+---------+---------+
| 10 | 100 |
| 20 | 200 |
+---------+---------+
2 rows in set (0.001 sec)
select * from rental;
+---------+
| bnumber |
+---------+
| 10 |
+---------+
1 row in set (0.001 sec)
Approach 2
drop trigger if exists t;
delimiter $$
create trigger t before delete on bike
for each row
begin
delete a from available a where a.bnumber = old.bnumber;
end $$
delimiter ;
delete b from bike b where
#bnumber = 10 and
not exists(select 1 from rental r where r.bnumber = b.bnumber);
select * from bike;
+---------+------+-------+------+
| bnumber | make | color | year |
+---------+------+-------+------+
| 10 | aaa | red | 2020 |
+---------+------+-------+------+
1 row in set (0.001 sec)
select * from available;
+---------+---------+
| bnumber | rack-id |
+---------+---------+
| 10 | 100 |
+---------+---------+
1 row in set (0.001 sec)
select * from rental;
+---------+
| bnumber |
+---------+
| 10 |
+---------+
1 row in set (0.001 sec)
Creating an AFTER DELETE instead could also help to skip the Foreign Key Constraint issue
CREATE TRIGGER trigger_name
AFTER DELETE
ON table_name FOR EACH ROW
trigger_body;
ref: https://www.mysqltutorial.org/mysql-triggers/mysql-after-delete-trigger/

soft delete on update in mysql with multiple schemas

I am in this situation:
Background
I have 2 database schemas called "prod" and "stg".
"prod" contains 2 tables called "parent" and "child"
"stg" only has the "parent" table
"parent" table defination is the same across "prod" and "stg" schemas.
In the case of deleting records, "parent" table is defined as soft delete (logically deletion, i.e. set delete_flg as "1") whereas the "child" table is true delete (physically remove the record)
Goal
I am trying to achieve the following goal:
when and only when both "prod"."parent" and "stg"."parent" are deleted (no matter physically or logically, or does not exist on one side) then automatically cascade a delete operation(physically remove) to the record in "prod"."child" table whose "SP_ID" matches the value in "parent".
For example, assuming I have
"prod"."parent"
+----+---------+--------+
| SP_ID | SP_NAME | DELETE_FLG |
+----+---------+--------+
| 1 | 1 | 1 |
+----+---------+--------+
"prod"."parent"
+----+---------+--------+
| SP_ID | SP_NAME | DELETE_FLG |
+----+---------+--------+
| 1 | 1 | 1 |
+----+---------+--------+
"stg"."parent"
+----+---------+--------+
| SP_ID | SP_NAME | DELETE_FLG |
+----+---------+--------+
| 1 | 1 | 0 |
+----+---------+--------+
"prod"."child"
+----+---------+
| SP_ID | JOB_KEY |
+----+---------+
| 1 | key |
+----+---------+
, if I execute a sql update "stg"."parent" set DELETE_FLG = 1 where SP_ID = 1, which logically delete the last "existing" record in "parent" table that has SP_ID 1, then the record in "prod"."child" will also be automatically phycially deleted by mysql.
Question
I have been thinking about making the SP_ID in the child table as a foreign key referencing the one in parent able (https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html)
however,
a) I don't know whether it is possible to reference multiple tables in differnet schemas, and
b) It seems mysql only support cascading same operation, i.e. delete on parent then delete the child OR update on parent then update the child. But in my case, I want a update on parent then delete the child.
Could somebody help me out here please?
Is this possible to be achieved in mysql? or I have to do this in application layer?
Table definition
CREATE TABLE `prod`.`parent` (
`SP_ID` varchar(20) NOT NULL COMMENT '',
`SP_NAME` varchar(100) NOT NULL COMMENT '',
`DELETE_FLG` tinyint(1) NOT NULL DEFAULT '0' COMMENT '',
PRIMARY KEY (`SP_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=''
CREATE TABLE `prod`.`child` (
`SP_ID` varchar(20) NOT NULL COMMENT '',
`JOB_KEY` varchar(11) NOT NULL,
PRIMARY KEY (`SP_ID`,`JOB_KEY`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=''
CREATE TABLE `stg`.`parent` (
`SP_ID` varchar(20) NOT NULL COMMENT '',
`SP_NAME` varchar(100) NOT NULL COMMENT '',
`DELETE_FLG` tinyint(1) NOT NULL DEFAULT '0' COMMENT '',
PRIMARY KEY (`SP_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=''
with the hint of using triggers, This is my solution which works:
Add 2 triggers(one is after update, on is after delete) to both prod and stg parent tables.
# after update trigger
CREATE DEFINER=`root`#`localhost` TRIGGER `stg`.`parent_AFTER_UPDATE` AFTER UPDATE ON `parent` FOR EACH ROW
BEGIN
IF (
select
count(*)
from(
select
*
from `prod`.`parent`
where `prod`.`parent`.id = old.id and `prod`.`parent`.delete_flg = 0
Union all
select
*
from `stg`.`parent`
where `stg`.`parent`.id = old.id and `stg`.`parent`.delete_flg = 0
) as a
) = 0 THEN
DELETE FROM `prod`.`child` WHERE `prod`.`child`.id = old.id;
END IF;
END
# after delete trigger
CREATE DEFINER=`root`#`localhost` TRIGGER `stg`.`parent_AFTER_DELETE` AFTER DELETE ON `parent` FOR EACH ROW
BEGIN
IF (
select
count(*)
from(
select
*
from `prod`.`parent`
where `prod`.`parent`.id = old.id and `prod`.`parent`.delete_flg = 0
Union all
select
*
from `stg`.`parent`
where `stg`.`parent`.id = old.id and `stg`.`parent`.delete_flg = 0
) as a
) = 0 THEN
DELETE FROM `prod`.`child` WHERE `prod`.`child`.id = old.id;
END IF;
END

MySQL recursive procedure to delete a record

I have a table, Models that consists of these (relevant) attributes:
-- -----------------------------------------------------
-- Table `someDB`.`Models`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `someDB`.`Models` (
`model_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
`type_id` SMALLINT UNSIGNED NOT NULL,
-- someOtherAttributes
PRIMARY KEY (`model_id`),
ENGINE = InnoDB;
+---------+---------+
| model_id| type_id |
+---------+---------+
| 1 | 4 |
| 2 | 4 |
| 3 | 5 |
| 4 | 3 |
+---------+---------+
And table Model_Hierarchy that shows the parent & child relationship (again, showing only the relevant attributes):
-- -----------------------------------------------------
-- Table `someDB`.`Model_Hierarchy`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `someDB`.`Model_Hierarchy` (
`parent_id` MEDIUMINT UNSIGNED NOT NULL,
`child_id` MEDIUMINT UNSIGNED NOT NULL,
-- someOtherAttributes,
INDEX `fk_Model_Hierarchy_Models1_idx` (`parent_id` ASC),
INDEX `fk_Model_Hierarchy_Models2_idx` (`child_id` ASC),
PRIMARY KEY (`parent_id`, `child_id`),
CONSTRAINT `fk_Model_Hierarchy_Models1`
FOREIGN KEY (`parent_id`)
REFERENCES `someDB`.`Models` (`model_id`)
ON DELETE CASCADE
ON UPDATE NO ACTION,
CONSTRAINT `fk_Model_Hierarchy_Models2`
FOREIGN KEY (`child_id`)
REFERENCES `someDB`.`Models` (`model_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
+-----------+----------+
| parent_id | child_id |
+-----------+----------+
| 1 | 2 |
| 2 | 4 |
| 3 | 4 |
+-----------+----------+
If there is a Model that is not a parent or child (at some point) of another Model whose type is 5, it is not valid and hence should be deleted.
This means that Model 1, 2 should be deleted because at no point do they have a model as parent or child with type_id = 5.
There are N levels in this hierarchy, but there are no circular relationship (ie. 1 -> 2; 2 -> 1 will not exist).
Any idea on how to do this?
Comments are dispersed throughout the code.
Schema:
CREATE TABLE `Models`
( -- Note that for now the AUTO_INC is ripped out of this for ease of data insertion
-- otherwise we lose control at this point (this is just a test)
-- `model_id` MEDIUMINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`model_id` MEDIUMINT UNSIGNED PRIMARY KEY,
`type_id` SMALLINT UNSIGNED NOT NULL
)ENGINE = InnoDB;
CREATE TABLE `Model_Hierarchy`
( -- OP comments state these are more like components
--
-- #Drew imagine b being a product and a and c being two different ways to package it.
-- Hence b is contained in both a and c respectively and separately (ie. customer can buy
-- both a and c), however, any change (outside of the scope of this question) to b is
-- reflected to both a and c. `Model_Hierarchy can be altered, yes (the project is
-- in an early development). Max tree depth is unknown (this is for manufacturing...
-- so a component can consist of a component... that consist of further component etc.
-- no real limit). How many rows? Depends, but I don't expect it to exceed 2^32.
--
--
-- Drew's interpretation of the the above: `a` is a parent of `b`, `c` is a parent of `b`
--
`parent_id` MEDIUMINT UNSIGNED NOT NULL,
`child_id` MEDIUMINT UNSIGNED NOT NULL,
INDEX `fk_Model_Hierarchy_Models1_idx` (`parent_id` ASC),
INDEX `fk_Model_Hierarchy_Models2_idx` (`child_id` ASC),
PRIMARY KEY (`parent_id`, `child_id`),
key(`child_id`,`parent_id`), -- NoteA1 pair flipped the other way (see NoteA2 in stored proc)
CONSTRAINT `fk_Model_Hierarchy_Models1`
FOREIGN KEY (`parent_id`)
REFERENCES `Models` (`model_id`)
ON DELETE CASCADE
ON UPDATE NO ACTION,
CONSTRAINT `fk_Model_Hierarchy_Models2`
FOREIGN KEY (`child_id`)
REFERENCES `Models` (`model_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION
)ENGINE = InnoDB;
CREATE TABLE `GoodIds`
( -- a table to determine what not to delete from models
`id` int auto_increment primary key,
`model_id` MEDIUMINT UNSIGNED,
`has_been_processed` int not null,
dtFinished datetime null,
-- index section (none shown, developer chooses later, as he knows what is going on)
unique index(model_id), -- supports the "insert ignore" concept
-- FK's below:
foreign key `fk_abc_123` (model_id) references Models(model_id)
)ENGINE = InnoDB;
To drop and start over from the top:
-- ------------------------------------------------------------
-- reverse order is happier
drop table `GoodIds`;
drop table `Model_Hierarchy`;
drop table `Models`;
-- ------------------------------------------------------------
Load Test Data:
insert Models(model_id,type_id) values
(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,5),(10,1),(11,1),(12,1);
-- delete from Models; -- note, truncate does not work on parents of FK's
insert Model_Hierarchy(parent_id,child_id) values
(1,2),(1,3),(1,4),(1,5),
(2,1),(2,4),(2,7),
(3,2),
(4,8),(4,9),
(5,1),
(6,1),(6,2),
(7,1),(7,10),
(8,1),(8,12),
(9,11),
(10,11),
(11,12);
-- Set 2 to test (after a truncate / copy paste of this below to up above):
(1,2),(1,3),(1,4),(1,5),
(2,1),(2,4),(2,7),
(3,2),
(4,8),(4,9),
(5,1),
(6,1),(6,2),
(7,1),(7,10),
(8,1),(8,12),
(9,1),
(10,11),
(11,12);
-- truncate table Model_Hierarchy;
-- select * from Model_Hierarchy;
-- select * from Models where type_id=5;
Stored Procedure:
DROP PROCEDURE if exists loadUpGoodIds;
DELIMITER $$
CREATE PROCEDURE loadUpGoodIds()
BEGIN
DECLARE bDone BOOL DEFAULT FALSE;
DECLARE iSillyCounter int DEFAULT 0;
TRUNCATE TABLE GoodIds;
insert GoodIds(model_id,has_been_processed) select model_id,0 from Models where type_id=5;
WHILE bDone = FALSE DO
select min(model_id) into #the_Id_To_Process from GoodIds where has_been_processed=0;
IF #the_Id_To_Process is null THEN
SET bDone=TRUE;
ELSE
-- First, let's say this is the parent id.
-- Find the child id's that this is a parent of
-- and they qualify as A Good Id to save into our Good table
insert ignore GoodIds(model_id,has_been_processed,dtFinished)
select child_id,0,null
from Model_Hierarchy
where parent_id=#the_Id_To_Process;
-- Next, let's say this is the child id.
-- Find the parent id's that this is a child of
-- and they qualify as A Good Id to save into our Good table
insert ignore GoodIds(model_id,has_been_processed,dtFinished)
select child_id,0,null
from Model_Hierarchy
where child_id=#the_Id_To_Process;
-- NoteA2: see NoteA1 in schema
-- you can feel the need for the flipped pair composite key in the above
UPDATE GoodIds set has_been_processed=1,dtFinished=now() where model_id=#the_Id_To_Process;
END IF;
-- safety bailout during development:
SET iSillyCounter = iSillyCounter + 1;
IF iSillyCounter>10000 THEN
SET bDone=TRUE;
END IF;
END WHILE;
END$$
DELIMITER ;
Test:
call loadUpGoodIds();
-- select count(*) from GoodIds; -- 9 / 11 / 12
select * from GoodIds limit 10;
+----+----------+--------------------+---------------------+
| id | model_id | has_been_processed | dtFinished |
+----+----------+--------------------+---------------------+
| 1 | 9 | 1 | 2016-06-28 20:33:16 |
| 2 | 11 | 1 | 2016-06-28 20:33:16 |
| 4 | 12 | 1 | 2016-06-28 20:33:16 |
+----+----------+--------------------+---------------------+
Mop up calls, can be folded into stored proc:
-- The below is what to run
-- delete from Models where model_id not in (select null); -- this is a safe call (will never do anything)
-- the above is just a null test
delete from Models where model_id not in (select model_id from GoodIds);
-- Error 1451: Cannot delete or update a parent row: a FK constraint is unhappy
-- hey the cascades did not work, can figure that out later
-- Let go bottom up for now. Meaning, to honor FK constraints, kill bottom up.
delete from Model_Hierarchy where parent_id not in (select model_id from GoodIds);
-- 18 rows deleted
delete from Model_Hierarchy where child_id not in (select model_id from GoodIds);
-- 0 rows deleted
delete from Models where model_id not in (select model_id from GoodIds);
-- 9 rows deleted / 3 remain
select * from Models;
+----------+---------+
| model_id | type_id |
+----------+---------+
| 9 | 5 |
| 11 | 1 |
| 12 | 1 |
+----------+---------+

Query on delete cascade not success in child table

I have created two table that have a condition like this.
Parent
CREATE TABLE IF NOT EXISTS `tbl_requestfix` (
`id_request` varchar(10) NOT NULL,
`waktu_tutup_request` datetime DEFAULT NULL,
`id_complaint` varchar(10) NOT NULL,
PRIMARY KEY (`id_request`),
KEY `FK_tbl_requestfix_tbl_detail_complaint` (`id_complaint`),
CONSTRAINT `FK_tbl_requestfix_tbl_detail_complaint`
FOREIGN KEY
(`id_complaint`) REFERENCES `tbl_detail_complaint` (`id_complaint`)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Child
CREATE TABLE IF NOT EXISTS `tbl_detail_complaint` (
`id_complaint` varchar(10) NOT NULL,
`complaint_2` text,
`timestamp_2` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id_complaint`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
When I am insert a row, there is no problem.
When I delete a row on parent, the row on child it still exist ?
Am I lost or wrong ?
DELETE FROM tbl_requestfix where id_request='001';
Thanks for the help. It so appreciated
You are calling this parent:
CREATE TABLE IF NOT EXISTS `tbl_requestfix` (
`id_request` varchar(10) NOT NULL,
`waktu_tutup_request` datetime DEFAULT NULL,
`id_complaint` varchar(10) NOT NULL,
PRIMARY KEY (`id_request`),
KEY `FK_tbl_requestfix_tbl_detail_complaint` (`id_complaint`),
CONSTRAINT `FK_tbl_requestfix_tbl_detail_complaint`
FOREIGN KEY
(`id_complaint`) REFERENCES `tbl_detail_complaint` (`id_complaint`)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
You are calling this child:
CREATE TABLE IF NOT EXISTS `tbl_detail_complaint` (
`id_complaint` varchar(10) NOT NULL,
`complaint_2` text,
`timestamp_2` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id_complaint`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
The fact is that you should be able to create Parent prior to creating Child. Parents come before the kids. But you can't create Parent first:
ERROR 1215 (HY000): Cannot add foreign key constraint
So I think you need to rethink this one.
Here is an example:
Schema:
-- drop table parent;
create table parent
( -- assume your have only one parent, ok bad example, it's early
id int auto_increment primary key,
fullName varchar(100) not null
)ENGINE=InnoDB;
-- drop table child;
create table child
( id int auto_increment primary key,
fullName varchar(100) not null,
myParent int not null,
CONSTRAINT `mommy_daddy` FOREIGN KEY (myParent) REFERENCES parent(id)
ON DELETE CASCADE ON UPDATE CASCADE
)ENGINE=InnoDB;
Test the cascade:
insert parent(fullName) values ('Robert Smith'),('Kim Billings'); -- id's 1 and 2
insert child(fullName,myParent) values ('Little Bobby',1),('Sarah Smith',1);
insert child(fullName,myParent) values ('Scout Billings',2),('Bart Billings',2);
select * from child;
+----+----------------+----------+
| id | fullName | myParent |
+----+----------------+----------+
| 1 | Little Bobby | 1 |
| 2 | Sarah Smith | 1 |
| 3 | Scout Billings | 2 |
| 4 | Bart Billings | 2 |
+----+----------------+----------+
delete from parent where id=1; -- delete Robert Smith
select * from child;
+----+----------------+----------+
| id | fullName | myParent |
+----+----------------+----------+
| 3 | Scout Billings | 2 |
| 4 | Bart Billings | 2 |
+----+----------------+----------+
There, the delete of the parent cascaded to clobber kids too

How to optimise that query?

I've a vote system which is designed like this:
CREATE TABLE `vote` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`weight` int(11) NOT NULL,
`submited_date` datetime NOT NULL,
`resource_type` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2963832 DEFAULT CHARSET=latin1;
CREATE TABLE `article_preselection_vote` (
`id` int(11) NOT NULL,
`article_id` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `IDX_9B145DEA62922701` (`article_id`),
KEY `IDX_9B145DEAA76ED395` (`user_id`),
CONSTRAINT `article_preselection_vote_ibfk_4` FOREIGN KEY (`article_id`) REFERENCES `article` (`id`),
CONSTRAINT `article_preselection_vote_ibfk_5` FOREIGN KEY (`id`) REFERENCES `vote` (`id`) ON DELETE CASCADE,
CONSTRAINT `article_preselection_vote_ibfk_6` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
v.weight can be +1 or -1, I need, given a bunch of articles ID, to get the sum of each positive vote (+1) and the sum of negative vote (-1) per articles id.
Then my result should be
article_id | vote_up | vote_down
-----------|---------|----------
1 | 36 | 20
-----------|---------|----------
68 | 12 | 56
-----------|---------|----------
25 | 90 | 12
-----------|---------|----------
I can get that result by doing the following request, but it's quite heavy and slow on 2,000,000 votes.
SELECT apv.article_id, COALESCE(SUM(up),0) as up, COALESCE(SUM(down),0) as down
FROM article_preselection_vote apv
LEFT JOIN(
SELECT id, weight up FROM vote WHERE weight > 0 AND vote.resource_type = 'article') v1 ON apv.id = v1.id
LEFT JOIN(
SELECT id, weight down FROM vote WHERE weight < 0 AND vote.resource_type = 'article') v2 ON apv.id = v2.id
WHERE apv.article_id IN (11702,11703,11704,11632,11652,11658)
GROUP BY apv.article_id
Any ideas?
Thanks in advance.
Subselects, IN (...) and GROUP BY in one query are killers.
You should redesign to have a more traditional solution:
Have a table with the votes article_id, votes_up, votes_down, vote_date, ...
Update (cron) the summary fields in your article table votes_up, votes_down, ... with one UPDATE.
That way, you can better handle the row/table locks and have fast queries
You can try a single join:
SELECT
apv.article_id,
SUM(COALESCE(weight, 0) > 0) AS up,
SUM(COALESCE(weight, 0) < 0) AS down
FROM article_preselection_vote apv
LEFT JOIN vote
ON apv.id = vote.id
AND vote.resource_type = 'article'
WHERE apv.article_id IN (11702, 11703, 11704, 11632, 11652, 11658)
GROUP BY apv.article_id
If you need to calculate this often it might be worthwhile to denormalize your database and store a cached copy of the results.
Instead of weighting the votes, why don't you just create two tables, one for up votes and one for down votes? The only thing it will complicate is vote combination, which will still be a simple sum of the counts of two different queries.
in a nut shell do something like this:
select * from article where article_id in (1,2,3);
+------------+-----------+---------------+-----------------+
| article_id | title | up_vote_count | down_vote_count |
+------------+-----------+---------------+-----------------+
| 1 | article 1 | 2 | 3 |
| 2 | article 2 | 2 | 1 |
| 3 | article 3 | 1 | 1 |
+------------+-----------+---------------+-----------------+
3 rows in set (0.00 sec)
drop table if exists article;
create table article
(
article_id int unsigned not null auto_increment primary key,
title varchar(255) not null,
up_vote_count int unsigned not null default 0,
down_vote_count int unsigned not null default 0
)
engine = innodb;
drop table if exists article_vote;
create table article_vote
(
article_id int unsigned not null,
user_id int unsigned not null,
score tinyint not null default 0,
primary key (article_id, user_id)
)
engine=innodb;
delimiter #
create trigger article_vote_after_ins_trig after insert on article_vote
for each row
begin
if new.score < 0 then
update article set down_vote_count = down_vote_count + 1 where article_id = new.article_id;
else
update article set up_vote_count = up_vote_count + 1 where article_id = new.article_id;
end if;
end#
delimiter ;
insert into article (title) values ('article 1'),('article 2'), ('article 3');
insert into article_vote (article_id, user_id, score) values
(1,1,-1),(1,2,-1),(1,3,-1),(1,4,1),(1,5,1),
(2,1,1),(2,2,1),(2,3,-1),
(3,1,1),(3,5,-1);
select * from article where article_id in (1,2,3);