Remove table entry if BOTH foreign keys were deleted - mysql

I have a table with 2 foreign keys that reference the same field within another table. I know how to define the foreign key constraint to remove my table entry if at least one of the two foreign keys become deleted. But instead I want to keep the table entry if at least one of the foreign keys still exist?
CREATE TABLE PrivateMessages
...
INDEX(FromEmail, ToEmail),
FOREIGN KEY(FromEmail, ToEmail)
REFERENCES Users(Email, Email)
ON UPDATE CASCADE
ON DELETE CASCADE,
...
The table stores messages between two users. I want to delete messages if both users don't exist any longer, only. Maybe, is there a better approach to realize this?

... I want to keep the table entry if at least one of the foreign keys still exist
I want to delete messages if both users don't exist any longer, only.
It can't be achieved just by defining a constraint.
Possible procedure:
Define a marked_for_delete bit not null default 0 column feature.
Whenever you want to delete a user, mark it for deletion, by an
update call.
Define either a before or an after type of delete trigger on
the table.
In the trigger body, check if the combo of fromEmail and toEmail
are marked for deletion, then execute a delete statement on the
parent table and the child combo will be deleted as your required
condition matched.

Related

How to update values of foreign key fields referencing to a parent table in MySQL?

I am migrating a table to a new format and as part of the process, I need to delete rows and condense them into a single row. However, these old rows are being referenced by other tables.
Is there a way to automatically update the values of the foreign keys referencing the old rows to the 'id' of the newly-created row or is this only possible manually going through all the referencing tables? I am using a number (id) to identify the rows.
The only other way possible is to go through each referencing table and update the old values with multiple sub-queries OR disable foreign key checks then delete the old rows, but this wouldn't update the values in child tables.
Error Message: SQL Error (1451): Cannot delete or update a parent row:
a foreign key constraint fails... (table names I can't show)
I suggest you to create a mapping between the Old and New Ids first, and then temporary introduce a new field called NewId what you will populate from that mapping. Next, once you have populated all new Ids, you can simply update your primary Id from the new one and delete the NewId column completely.
Of course, before you start any key updates you will require to disable a foreign key constraint, read this post about how to do it.

SQL: Cannot delete or update parent row: a foreign key constraint fails

Whenever I try to delete a survey from table "survey" like this:
DELETE FROM surveys WHERE survey_id = 77
It prompts me an error stated below:
#1451 - Cannot delete or update a parent row: a foreign key constraint fails ('user_surveys_archive', CONSTRAINT
'user_surveys_archive_ibfk_6' FOREIGN KEY ('user_access_level_id')
REFERENCES 'user_surveys' ('user_access_level_id') ON DELETE NO ACTION
)
First thing: I do not have any such table with this name "user_surveys_archive_ibfk_6"
2nd thing: There is no record of this survey in other tables.
Any idea on how can I delete this record of fix this issue?
Edit
This is the line I found when I export the table Constraints for table surveys
ALTER TABLE `surveys`
ADD CONSTRAINT `surveys_ibfk_1` FOREIGN KEY (`survey_type_id`) REFERENCES `survey_types` (`survey_type_id`) ON DELETE CASCADE ON UPDATE NO ACTION;`
You will need to first remove or update some rows in table user_surveys_archive.
Those rows are related to rows in the user_surveys table.
Likely, there's a foreign key constraint defined on table user_surveys that references rows in surveys you are attempting to delete.
(You'd need to check the foreign key definition, quickest way to get that is a
SHOW CREATE TABLE user_surveys
And look for REFERENCES surveys. (Likely, its a column named survey_id, but we're just guessing without looking at the definitions of the foreign key constraints.)
To find the rows in user_surveys_archive that are preventing the DELETE from happening...
SELECT a.*
FROM user_surveys_archvive a
JOIN user_surveys u
ON u.user_access_level_id = a.user_access_level_id
JOIN surveys s
ON s.survey_id = u.survey_id -- change this to whatever the FK is
WHERE s.survey_id = 77
It's likely that the foreign key constraint from user_surveys to surveys is defined with ON DELETE CASCADE. The attempt to delete rows from surveys identifies rows in user_surveys that should automatically be removed.
The attempt to automatically remove the rows from user_surveys is what's violating the foreign key constraint defined in user_surveys_archive. And that foreign key is not defined with ON DELETE CASCADE.
(The other possibility is that there's a trigger defined that's doing some DML operations, but that would be odd.)
Once you identify the rows, you need to decide what changes to make to allow you to remove rows from surveys.
You can either DELETE the rows, or UPDATE them.
To delete the rows from user_surveys_archive, modify the query above and replace SELECT with DELETE. If the user_access_level_id column in user_surveys_archive allows for NULL values, you can do an update.
Replace SELECT a.* FROM with UPDATE, and add SET a.user_access_level_id = NULL on a line above the WHERE clause...
UPDATE user_surveys_archvive a
JOIN user_surveys u
ON u.user_access_level_id = a.user_access_level_id
JOIN surveys s
ON s.survey_id = u.survey_id -- change this to whatever the FK is
SET a.user_access_level_id = NULL
WHERE s.survey_id = 77
(It seems strange to me that name of the foreign key column is user_access_level_id. But its just a column name, it could be named anything... seems odd to me because of the conventions and patterns that we follow in naming foreign key columns.)
You should allow the foreign key to be NULL and then choose ON DELETE SET NULL.
Personally I would recommend using both "ON UPDATE CASCADE" as well as "ON DELETE SET NULL" to avoid unnecessary complications, however your setup may dictate a different approach.
Hope this helps.
The survey_id of 77 must be referenced in a table possibly named user_surveys_archive. The error is saying the name of the foreign key restraint which is the user_surveys_archive_ibfk_6. If you need to delete survey_id = 77 you will have to delete any child records or other records that reference it as a foreign key.
Edit After posting my answer I saw the comments above. atif the foreign key in the user_surveys_archive table may be named differently but still equal the value of 77 that you are trying to delete, and/or be referencing the survey_id. If that is not the case then there are some other problems within the database. You could try and look at the code for the FK to see how it was made and what fields it is referencing, or run a query to see if there are any records in the user_surveys_archive where the user_access_level_id is = 77.
Edit 2 spencer7593's answer lays out how to run some of the queries mentioned in my answer.

First experience of Foreign keys, what've I done wrong?

I've been hinted into giving foreign keys a go as I'm trying to get better at database programming. The problem was, if a job in the jobs table was deleted, it left orphan messages relating to it in the messages table. So foreign keys were introduced to me to be the solution.
However, I've gotten this when trying to do it, and I'm not sure what it's saying, plus, I'm not even sure which way round I should be setting the restriction, from the job to the messages, or vice versa. :(
Here's the error:
#1452 - Cannot add or update a child row: a foreign key constraint fails (`nzua9c8_tasks`.<result 2 when explaining filename '#sql-2929_701930'>, CONSTRAINT `#sql-2929_701930_ibfk_1` FOREIGN KEY (`id`) REFERENCES `jobs` (`id`) ON UPDATE NO ACTION)
Also, the interface for PHPMyAdmin suggested that when I delete a task, the message ID would be "restricted". I didn't have an option for delete. I want any messages with a "job_id" that matches an id in the jobs table to be deleted too.
Thanks for any help offered.
You want to add on delete cascade to your foreign key definition (just after on update no action in your script).
So it would be something like:
alter table messages add foreign key (id) references jobs (id) on delete cascade
More can be found here.
If one job contains several messages (1:N relation) then message table should have foreign key to the jobs table.
Your error message basically says that you cant update/delete row when there is something referencing it by foreign constraint.
So if table jobs have records:
id name
1 job1
2 job2
And table of messages have records
id name job_id
1 mes1 1
2 mes2 2
Then you cant just drop record from first table. You should at first update referencing record in message table.
Also, you can change foreign key policy in such way that dependant records will be destroyed/updated automatically.
Foreign keys with corresponding "constraints" are for ensuring "referential integrity" and will solve the "dangling reference" problem you mentioned.
The "downside" to foreign keys is that the data must be populated into the database in the correct order:
If the rows in table "A" contain a foreign key pointer to one or more rows in table "B" then the rows in table "B" must be created before the rows in Table "A" can be created - otherwise the constraint will throw an error.

MySql Foreign keys: ON DELETE NO ACTION behavour - how to leave info in referenced field?

I have two tables, one 'users' and one 'orders', where the order table has a foreign key reference to the user id table. (I'm using email address as user id in this case.)
What I want to accomplish is this:
If the user table id is updated (= email address is changed), this will reflect in the referenced order table user_id field. (This works fine using ON UPDATE CASCADE directive - no problem here!)
If the user is deleted from the user table, the order will remain, KEEPING the referenced user_id.
I'm having problem with the second goal:
If I use ON DELETE CASCADE on the referenced user id field, the order row is of course deleted.
If I use ON DELETE NO ACTION, I get an error when I try to remove the user. (#1451 - Cannot delete or update a parent row: a foreign key constraint fails)
If I use ON DELETE SET NULL, I can delete the user row, but the referenced info is set to null so I can't track down who made that order...
Is there a way to allow removal of the user from the user table while keeping the referenced user_id information in the orders table?
Two options:
Don't actually delete the user, just mark them as deleted by setting a deleted field.
or:
Remove the foreign key constraint.
I would recommend the first option. Taking the second option can lead to data inconsistencies. Besides, just having the value of the key without the corresponding data in the other table will not help you much anyway.
Actually there is another alternative - replace the user table's email address key with an autoincrement INT.
Then you could just copy the user attribute (ugh, denormalized) into the order (I guess you could justify it as being the 'email address of the ordering user at the time of order').
Then ON DELETE SET NULL could reset the foreign key INT but not the copied attribute (email address).

Setting up foreign keys in phpMyAdmin?

I'm setting up a database using phpMyAdmin. I have two tables (foo and bar), indexed on their primary keys. I am trying to create a relational table (foo_bar) between them, using their primary keys as foreign keys.
I created these tables as MyISAM, but have since changed all three to InnoDB, because I read that MyISAM doesn't support foreign keys. All id fields are INT(11).
When I choose the foo_bar table, click the "relation view" link, and try to set the FK columns to be database.foo.id and database.bar.id, it says "No index defined!" beside each column.
What am I missing?
Clarification/Update
For the sake of simplicity, I want to keep using phpMyAdmin. I am currently using XAMPP, which is easy enough to let me focus on the PHP/CSS/Javascript, and it comes with phpMyAdmin.
Also, although I haven't been able to set up explicit foreign keys yet, I do have a relational table and can perform joins like this:
SELECT *
FROM foo
INNER JOIN foo_bar
ON foo.id = foo_bar.foo_id
INNER JOIN bar
ON foo_bar.bar_id = bar.id;
It just makes me uncomfortable not to have the FKs explicitly defined in the database.
If you want to use phpMyAdmin to set up relations, you have to do 2 things. First of all, you have to define an index on the foreign key column in the referring table (so foo_bar.foo_id, in your case). Then, go to relation view (in the referring table) and select the referred column (so in your case foo.id) and the on update and on delete actions.
I think foreign keys are useful if you have multiple tables linked to one another, in particular, your delete scripts will become very short if you set the referencing options correctly.
EDIT: Make sure both of the tables have the InnoDB engine selected.
phpMyAdmin lets you define foreign keys using their "relations" view. But since, MySQL only supports foreign constraints on "INNO DB" tables, the first step is to make sure the tables you are using are of that type.
To setup a foreign key so that the PID column in a table named CHILD references the ID column in a table named PARENT, you can do the following:
For both tables, go to the operations tab and change their type to "INNO DB"
Make sure ID is the primary key (or at least an indexed column) of the PARENT table.
In the CHILD table, define an index for the PID column.
While viewing the structure tab of the CHILD table, click the "relation view" link just above the "add fields" section.
You will be given a table where each row corresponds to an indexed column in your CLIENT table. The first dropdown in each row lets you choose which TABLE->COLUMN the indexed column references. In the row for PID, choose PARENT->ID from the dropdown and click GO.
By doing an export on the CHILD table, you should see a foreign key constraint has been created for the PID column.
This is a summary of a Wikipedia article. It specifies the different types of relationships you can stipulate in PHPmyadmin. I am putting it here because it is relevant to #Nathan's comment on setting the foreign keys options for "on update/delete" but is too large for a comment.
CASCADE
Whenever rows in the master (referenced) table are deleted (resp. updated), the respective rows of the child (referencing) table with a matching foreign key column will get deleted (resp. updated) as well. This is called a cascade delete (resp. update[2]).
RESTRICT
A value cannot be updated or deleted when a row exists in a foreign key table that references the value in the referenced table. Similarly, a row cannot be deleted as long as there is a reference to it from a foreign key table.
NO ACTION
NO ACTION and RESTRICT are very much alike. The main difference between NO ACTION and RESTRICT is that with NO ACTION the referential integrity check is done after trying to alter the table. RESTRICT does the check before trying to execute the UPDATE or DELETE statement. Both referential actions act the same if the referential integrity check fails: the UPDATE or DELETE statement will result in an error.
SET NULL
The foreign key values in the referencing row are set to NULL when the referenced row is updated or deleted. This is only possible if the respective columns in the referencing table are nullable. Due to the semantics of NULL, a referencing row with NULLs in the foreign key columns does not require a referenced row.
SET DEFAULT
Similar to SET NULL, the foreign key values in the referencing row are set to the column default when the referenced row is updated or deleted.
In phpmyadmin, you can assign Foreign key simply by its GUI. Click on the table and go to Structure tab. find the Relation View on just bellow of table (shown in below image).
You can assign the forging key from the list box near by the primary key.(See image below). and save
corresponding SQL query automatically generated and executed.
For those new to database .... and need to ALTER an existing table. A lot things seem to be pretty straightforward, but there is always something ... between A and B.
Before anything else, take a look at this.
Make sure you have P_ID (parent ID on both parent and child table).
Of course it will be already filled in the parent. Not necessarily in the child in a true and final way. So for instance P_ID #3 (maybe many times in the child table will be pointing to original P_ID at parent table).
Go to SQL tab (I am using phpMyAdmin, should be similar in other ones) and do this command:
ALTER TABLE child_table_name
ADD FOREIGN KEY (P_ID)
REFERENCES parent_table_name (P_ID)
Click on child table, than structure, finally on relational view. Finish your DB planning there. There was a nice answer before this one about cascade, restrict, etc.
Of course it could be done by commands...
Foreign key means a non prime attribute of a table referes the prime attribute of another
*in phpMyAdmin* first set the column you want to set foreign key as an index
then click on RELATION VIEW
there u can find the options to set foreign key
InnoDB allows you to add a new foreign key constraint to a table by using ALTER TABLE:
ALTER TABLE tbl_name
ADD [CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (index_col_name, ...)
REFERENCES tbl_name (index_col_name,...)
[ON DELETE reference_option]
[ON UPDATE reference_option]
On the other hand, if MyISAM has advantages over InnoDB in your context, why would you want to create foreign key constraints at all. You can handle this on the model level of your application. Just make sure the columns which you want to use as foreign keys are indexed!
Don't forget that the two columns should have the same data type.
for example if one column is of type INT and the other is of type tinyint you'll get the following error:
Error creating foreign key on [PID column] (check data types)
This is old thread but answer because if useful to anyone.
Step 1. Your Db Storage Engine set to InnoDB
Step 2. Create Primary Table
here customer is primary table and customer_id is primary key
Step 3. create foreign key table and give index
here we have customer_addresses as related table and store customer addresses, so here customer_id relation with customer table
we can select index directly when create table as below
If you forgot to give index when create a table, then you can give index from the structure tab of table as below.
Step 4. Once index give to the field, Go to structure tab and click on Relation View as shown in below pic
Step 5. Now select the ON DELETE and ON UPDATE what you want to do, Select column from current table, select DB (SAME DB), select relation table and primary key from that table as shown in below pic and Save it
Now check if relation are give successfully, go to foreign table data list and click on foreign key value, you will redirect to primary table record, then relation made successfully.
Make sure you have selected your mysql storage engine as Innodb and not MYISAM as Innodb storage engine supports foreign keys in Mysql.
Steps to create foreign keys in phpmyadmin:
Tap on structure for the table which will have the foreign key.
Create INDEX for the column you want to use as foreign key.
Tap on Relation view, placed below the table structure
In the Relation view page, you can see select options in front of the field (which was made an INDEX).
UPDATE CASCADE specifies that the column will be updated when the referenced column is updated,
DELETE CASCADE specified rows will be deleted when the referenced rows are deleted.
Alternatively, you can also trigger sql query for the same
ALTER TABLE table_name
ADD CONSTRAINT fk_foreign_key_name
FOREIGN KEY (foreign_key_name)
REFERENCES target_table(target_key_name);
Step 1:
You have to add the line:
default-storage-engine = InnoDB
under the [mysqld] section of your mysql config file (my.cnf or my.ini depending on your OS) and restart the mysqld service.
Step 2:
Now when you create the table you will see the type of table is: InnoDB
Step 3:
Create both Parent and Child table. Now open the Child table and select the column U like to have the Foreign Key:
Select the Index Key from Action Label as shown below.
Step 4:
Now open the Relation View in the same child table from bottom near the Print View as shown below.
Step 5:
Select the column U like to have the Foreign key as Select the Parent column from the drop down.
dbName.TableName.ColumnName
Select appropriate Values for ON DELETE and ON UPDATE
First set Storage Engine as InnoDB
then the relation view option enable in structure menu
You can also do it with a SQL command, like so.
ALTER TABLE employees
ADD CONSTRAINT fk_companyid FOREIGN KEY (companyid)
REFERENCES companies (id)
ON DELETE CASCADE;
In this example, if a row from companies is deleted, all employees with that companyid are also deleted.
From the official MySQL documentation at https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html:
MySQL requires indexes on foreign keys and referenced keys so that
foreign key checks can be fast and not require a table scan.