I have a table with, let's say, the following columns:
Name , Parent Name, ID
Let's also say that there are three entries where Parent Name is Null (meaning they are the top-most parent) - F_one, G_one, and H_one.
If I want to delete all the descendants of one of those parents (G_one, why not?) meaning all the children of G_one, all the children of those children, and the children of those, and so on all the way until the terminal level where, that row's Name does not exist as a Parent Name for any other entry.
Is that possible to be done easily, maybe with a single query?
Bonus, is there a way to select all of the G_one lineage so I can manipulate it to my whim and will?
Can assume:
-No Children are shared among parents
Cannot assume:
-A discrete or even consistent number of sub-levels.
As #Marc B's suggestion, a FORIEGN KEY with ON DELETE CASCADE would achieve this.
If you haven't one, you can add it now:
If there is a UNIQUE constraint on Name (I assume the PRIMARY key os ID), skip thi sstep. If there ism't one, create it:
ALTER TABLE tableX
ADD CONSTRAINT unique_Name
UNIQUE (Name) ;
If the previous step succeeded, add the FOREIGN KEY:
ALTER TABLE tableX
ADD CONSTRAINT fk_Name_ParentName
FOREIGN KEY (ParentName)
REFERENCES tableX (Name)
ON UPDATE CASCADE
ON DELETE CASCADE ;
If the previous step succeeded, you can now delete your rows with one statement:
DELETE
FROM tableX
WHERE ParentName = 'G_one' ;
This should result in: Y rows affected.
I can't test this, but I think something like this might work:
CREATE TRIGGER casc_del AFTER DELETE on tblName
FOR EACH ROW
DELETE FROM tblName
WHERE tblName.parent_name is not null
AND tblName.parent_name NOT IN (SELECT name FROM tblName)
More about triggers in MySQL can be found here. Note: this approach would only work in 5.02 or later.
Related
I'm using MySQL. Let's assume I have a table hierarchy with two columns: id, parent_id.
The parent_id refers to id of other row of the same table, so I have the foreign key there.
The hierarchy table contains some data, but they are not relevant now.
I also have a second table called new_hierarchy_entries that has the same columns, but there are no foreign key restrictions set.
new_hierarchy_entries contains:
id parent_id
2 1
1 null
Now I want to copy all the rows from new_hierarchy_entries into hierarchy. When I run naively:
INSERT INTO hierarchy SELECT * FROM new_hierarchy_entries
I get error: Cannot add or update a child row: a foreign key constraint fails (my_db.hierarchy, CONSTRAINT hierarchy_ibfk_2 FOREIGN KEY (parent_id) REFERENCES hierarchy (id))
Of course, if the rows are inserted one by one, the first row (id=2, parent=1) cannot be inserted, because there is no row with id=1 in table hierarchy.
On the other hand, if all rows were added at once, then the constraints would be satisfied. So how can I copy the rows in such a way that I'm sure that constraints are satisfied after the copying, but they may not be satisfied while copying?
Sorting rows of new_hierarchy_entries by id will not help. I cannot assume that parent_id < id in the same row.
Sorting rows of new_hierarchy_entries by the hierarchy (using tree terminology, give me leaves first, then their parents etc.) would help, but I'm not sure how to do that in MySQL query.
I played with the idea of temporarily turning the FOREIGN_KEY_CHECKS off. But then I could insert inconsistent data and I wouldn't find out. Turning FOREIGN_KEY_CHECKS on doesn't make the database check consistency of all the data. It would take too much resources anyway.
This is tricky. I don't know any way to make MySQL re-check foreign key references after enabling FOREIGN_KEY_CHECKS.
You could check yourself for orphan rows, and if there are any, roll back.
BEGIN;
SET SESSION FOREIGN_KEY_CHECKS=0;
INSERT INTO hierarchy SELECT * FROM new_hierarchy_entries;
SET SESSION FOREIGN_KEY_CHECKS=1;
SELECT COUNT(*) FROM hierarchy AS c
LEFT OUTER JOIN hierarchy AS p ON p.id=c.parent_id
WHERE p.id IS NULL;
-- if count == 0 then...
COMMIT;
-- otherwise ROLLBACK and investigate the bad data
One other possibility is to use INSERT with the IGNORE option, which will skip failed rows. Then repeat the same statement in a loop, as long as you see "rows affected" more than 0.
INSERT IGNORE INTO hierarchy SELECT * FROM new_hierarchy_entries;
INSERT IGNORE INTO hierarchy SELECT * FROM new_hierarchy_entries;
INSERT IGNORE INTO hierarchy SELECT * FROM new_hierarchy_entries;
...
Let's assume I have two tables parents and children. Naturally parents may have many children with one-to-many association. Is there any construct within MySQL or PostgreSQL which would allow to limit the number of associated objects, something like:
FOREIGN KEY (parent_id)
REFERENCES parent(id)
LIMIT 3
Does anything like this exists or do we need to have a custom trigger?
Not in the definition of foreign keys. I would solve that by adding a sequential number for each child per parent like this (code for PostgreSQL, the principal is standard SQL):
CREATE TABLE child (
child_id serial PRIMARY KEY
, parent_id int NOT NULL REFERENCES parent
, child_nr int NOT NULL
, CHECK (child_nr BETWEEN 1 AND 3)
, UNIQUE (parent_id, child_nr)
);
This way you can have children 1 through 3 for each parent or some or none of those. But no others.
Since you have a natural PK with (parent_id, child_nr) now, you could drop the surrogate PK column child_id. But I like to have a single-column surrogate PK for almost every table ...
You could use a trigger to limit the number, which checks how many children are there already before inserting a new one. But you'd run into concurrency issues, and it's more expensive, less reliable, easier to circumvent and vendor-specific.
How to manage child_nr?
The RDBMS just enforces (reliably) that no illegal state can ever exist in the table. How you figure out the next child_nr is up to you. Many different approaches are possible.
For just three children, you could insert all children automatically when creating a parent (with a trigger, a rule or in your application). With given (parent_id, child_nr) and additional columns NULL.
Then you would only allow UPDATE and not INSERT or DELETE for the child table (GRANT / REVOKE), or even make sure with another trigger, so that superusers cannot circumvent it. Make the FK to parent with ON DELETE CASCADE, so children die with the parent automatically.
Alternative
Somewhat less reliable, but cheaper: Keep a running count of children in the parent table and restrict it to be <= 3. Update it with every change in the child table with triggers. Be sure to cover all possible ways to alter data in the child table.
I want to delete specific rows from 8 tables.
My problem is that the rows are connected with foreign key.
How can I delete all the data that connected to the specific rows that I want to delete?
My tables include definition tables (like id, name ,max value, min value...),
data tables (like id, user_id, definition_id,....) and history tables (save every change in data table).
I thought to use delete on cascade command but I could not find a way to use it.
DELETE CASCADE is an attribute of the foreign key constraint. Unfortunately it's not something you can use as an option with a DELETE statement (which would be really cool actually)
If your foreign keys have not been declared as cascading you need to "work your way up".
Unfortunately you did not show us your real table structure so let's assume something like this:
main_table (main_id)
child_one (id, main_id)
child_two (id, id_one)
child_three (id, id_two)
(I know you said 8 tables, but for the sake of the demonstration I shortened it a bit, but that doesn't change the underlying "strategy")
Assuming you want to delete the row with main_id = 42 from `main_table:
You first need to delete the rows from child_three using something like this:
delete from child_three
where id_two in (select id
from child_two
where id_one in (select id
from child_one
where main_id = 42);
Then delete the rows from child_two:
delete from child_two
where id_one in (select id
from child_one
where main_id = 42);
Then child_one:
delete from child_one
where main_id = 42;
And finally the main table:
delete from main_table
where id = 42;
Some SQL clients can actually generate those statements for you. I don't know if SQL Developer can though.
I assume that you use InnoDB Engine, since you are talking about foreign keys,
Easier will be to define properly the table so that a deletion will act as a cascade deletion.
CONSTRAINT `myForeignKey` FOREIGN KEY (`typeId`)
REFERENCES `types` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
Here is alink with a proper create table statement:
How do I use on delete cascade in mysql?
What is the best way to insert rows into tables with references 1 to 1 of each other?
I mean, in a MySQL 5.5 and tables InnoDB, I have a database design similar to the following
The problem arises when we try to insert rows in table1 and table2. Since there is no multi-table insert in MySQL, I can not insert a row becouse the foreign keys are NOT NULL fields in both tables and should be inserted simultaneously in both.
Which is the bes way to solve this problem?
I have in mind 3 possible solutions, but I want to know if there are more than these or which is the best and why.
Set the foreign key field as NULLABLE and after insert one row in a table, insert the other one and afterwards, update de first one.
Just as indicated above but with an special value like -1. First, insert in one table with foreign key = -1 that is equivalent to NULL but avoiding set the field as NULLABLE. Afterwards, we insert the row in the other table and update the first one inserted.
Create a relational table between both though it is not really necessary because it is a 1 to 1 ratio
Thanks!!
EDIT
I briefly explain what I need this circular relationship: It is a denormalization from the parent table to one of its childs. It is made in order of high performance to have always the reference of the best ranked child from a parent table.
I'll make this an answer as I feel this is a design flaw.
First, if the two tables are in true 1:1 relationship, why don't you just have one table?
Second, if it's not a true 1:1 relationship but a supertype-subtype problem, you don't need this circular foreign keys either. Lets say table1 is Employee and table2 is Customer. Off course most customers are not employees (and vice-versa). But sometimes a customer may be an employee too. This can be solved having 3 tables:
Person
------
id
PRIMARY KEY: id
Employee
--------
personid
lastname
firstname
... other data
PRIMARY KEY: personid
FOREIGN KEY: personid
REFERENCES Person(id)
Customer
--------
personid
creditCardNumber
... other data
PRIMARY KEY: personid
FOREIGN KEY: personid
REFERENCES Person(id)
In the scenario you describe you have two tables Parent and Child having 1:N relationship. Then, you want to store somehow the best performing (based on a defined calculation) child for every parent.
Would this work?:
Parent
------
id
PRIMARY KEY: id
Child
-----
id
parentid
... other data
PRIMARY KEY: id
FOREIGN KEY: parentid
REFERENCES Parent(id)
UNIQUE KEY: (id, parentid) --- needed for the FK below
BestChild
---------
parentid
childid
... other data
PRIMARY KEY: parentid
FOREIGN KEY: (childid, parentid)
REFERENCES Child(id, parentid)
This way, you enforce the wanted referential integrity (every BestChild is a Child, every Parent has only one BestChild) and there is no circular path in the References. The reference to the best child is stored in the extra table and not in the Parent table.
You can find BestChild for every Parent by joining:
Parent
JOIN BestChild
ON Parent.id = BestChild.parentid
JOIN Child
ON BestChild.childid = Child.id
Additionally, if you want to store best children for multiple performance tests (for different types of tests, or tests in various dates), you can add a test field, and alter the Primary Key to (test, parentid):
BestChild
---------
testid
parentid
childid
... other data
PRIMARY KEY: (testid, parentid)
FOREIGN KEY: (childid, parentid)
REFERENCES Child(id, parentid)
FOREIGN KEY: testid
REFERENCES Test(id)
I'd create a blackhole table and put a trigger on that to take care of inserts
CREATE TABLE bh_table12 (
table1col varchar(45) not null,
table2col varchar(45) not null
) ENGINE = BLACKHOLE
and put a trigger on that to take care of inserts
DELIMITER $$
CREATE TRIGGER ai_bh_table12_each AFTER INSERT ON bh_table12 FOR EACH ROW
BEGIN
DECLARE mytable1id integer;
DECLARE mytable2id integer;
SET foreign_key_checks = 0;
INSERT INTO table1 (table1col, table2_id) VALUES (new.table1col, 0);
SELECT last_insert_id() INTO mytable1id;
INSERT INTO table2 (table2col, table1_id) VALUES (new.table2col, table1id);
SELECT last_insert_id() INTO mytable2id;
UPDATE table1 SET table2_id = mytable2id WHERE table1.id = mytable1id;
SET foreign_key_checks = 1;
END $$
DELIMITER ;
Note that actions in a trigger are part of one transaction (when using InnoDB or likewise), so an error in the trigger will rollback partial changes.
Note on your table structure
Note that if it's a 1-on-1 table, you only need to put a table2_id in table1 and no table1_id in table2 (or visa versa).
If you need to query table1 based on table2 you can just use:
SELECT table1.* FROM table1
INNER JOIN table2 on (table2.id = table1.table2_id)
WHERE table2.table2col = 'test2'
Likewise for the other way round
SELECT table2.* FROM table2
INNER JOIN table1 on (table2.id = table1.table2_id)
WHERE table1.table1col = 'test1'
Links:
http://dev.mysql.com/doc/refman/5.1/en/blackhole-storage-engine.html
http://dev.mysql.com/doc/refman/5.1/en/triggers.html
I feel this is an important question, and I haven't found any 100% satisfying answer throughout the web. The 2 answers that you have given are the best ones I found, yet they are not 100% satisfactory.
Here's why :
The reason why Emilio cannot put his best child inside his parent table is pretty simple, I presume, because I share the same problem : not every child will be labelled as a parent's best child. So he would still need to store information on other children somewhere else. In that case, he would have some information about the best children in their parent's table, and other children in a separate database. This is a huge mess. For example, the day he wants to change the data structure about children, he needs to change it in both tables. Every time he writes a query on all children, he should query both tables, etc...
the reason why Emilio cannot just set the best child foreign key to nullable (I presume for Emilio, but for me it would be very strict), is that he needs to be sure that a parent always has a best child. In Emilio's case it's maybe not very easy to imagine, but in mine, I cannot have the equivalent of the parent have no child.
Thus I would have tended to think that the solution with setting foreign_key_checks to zero would be best, but here is the problem :
after setting foreign_key_checks back to 1, there is no check on data's consistency. Thus, you have a risk of making mistakes in the meantime. You can consider that you won't, but still it is not a very clean solution.
I have a table that stores parent and child records in it.
I was trying to create a trigger that would delete all child records when the parent is deleted:
Delete From tbl Where ParentId = OLD.Id
While I can save the trigger successfully, when deleting I get this error:
ERROR 1442: Can’t update table ‘tbl′ in stored function/trigger because it is already used by statement which invoked this
What am I doing wrong?
It appears that this is not possible:
You cannot DELETE rows in the table
that activated trigger.
Some other options you might think about:
Write application logic that deletes the parent and child rows, and call this application logic whenever you want to delete a parent record, instead of deleting it directly.
Cascade delete relationship on the same table, which appears to be possible.
A cleanup process that routinely clears out orphaned child records.
(suggested by #Chris) Separate out the child records from the parent records by adding another table.
s. It is possible to do a Delete Cascade of child records in the same table. I found this post in the MYSQL forums had the answer. Here's how I got it to work.
I had to make sure that the primary id's parent was set to NULL.
I had to make sure that the primary id and parent id's were set to exactly the same kind of field, such as INT.
I also had to make sure the primary id was set to auto increment.
From the MYSQL forum:
create table edges(
ID int PRIMARY KEY,
parentid int,
unique key(id, parentid),
foreign key(parentID) references edges(ID) ON DELETE CASCADE ON UPDATE CASCADE
) engine=innodb;
insert into edges(ID,parentID) values (1,null),(2,1),(3,1),(4,2);
Now do this and watch the parent and all children delete cascade:
delete from edges where id=2;
select * from edges will then show two records left. Record 1 and record 3.
This was huge for my project, a gallery where users could create albums inside of albums inside of albums.