Opposite of RESTRICT in MySQL Foreign Key On Delete? - mysql

I'm in the process of redesigning some application security log tables (things like when users log in, access different files, etc.) to address some changing requirements. They were originally made with MyISAM, but don't really get accessed that often and switching to InnoDB and adding a bunch of foreign keys for data integrity would really be more beneficial. Since I have to remake the tables anyway, I figure this is as good a time as ever to make the switch.
For the most part, everything is straightforward foreign keys and works as expected. The only part that where I'm trying something weird and hitting problems is with user_ids. Each record in these log tables is associated with a user_id, and I want to make sure the given user_id exists when a record is inserted. Adding a foreign key that references the user table solves that problem - simple stuff. Here are some concise, representative tables:
The User Table
CREATE TABLE tbl_user (
id INT(10) NOT NULL AUTO_INCREMENT,
first_name VARCHAR(50),
PRIMARY KEY(id)
) ENGINE=InnoDB;
Example Log Table
CREATE TABLE tbl_login_time (
id INT(10) NOT NULL AUTO_INCREMENT,
user_id INT(10) NOT NULL,
login_at TIMESTAMP NOT NULL,
PRIMARY KEY(id),
CONSTRAINT 'tbl_login_time_fk_1` FOREIGN KEY (user_id) REFERENCES tbl_user
ON UPDATE CASCADE ON DELETE ???
) ENGINE=InnoDB;
My problem is that I want the foreign key enforced for inserts, updates to be cascaded, but deleting records in tbl_user to not affect tbl_login_time at all. Normally users get marked as inactive, but every once in awhile a user gets deleted entirely yet the logs need to be maintained.
The MySQL docs lists 6 options for ON DELETE, and none of them sound appropriate:
RESTRICT: Would prevent the deletion in tbl_user.
NO ACTION: Gets evaluated just like RESTRICT.
CASCADE: Would delete in tbl_user like I want, but also in tbl_login_time.
SET NULL: Would delete in tbl_user, and leave the row in tbl_login_time but nulls out the data. Close but no cigar.
SET DEFAULT: MySQL recognizes it, but rejects it.
Omit ON DELETE: Equivalent to RESTRICT.
I've never used a foreign key like this before (enforce INSERT and UPDATE but not DELETE), and after reading a lot of other questions it doesn't seem like anyone else does either. That should probably tell me this is the wrong approach, but can it work somehow?

My problem is that I want the foreign key enforced for inserts,
updates to be cascaded, but deleting records in tbl_user to not affect
tbl_login_time at all.
You can't accomplish that with a foreign key constraint.
In some applications, ON DELETE SET NULL makes sense. But your application is essentially a log file stored in a SQL database. You have a significant problem in that you want to delete identifying information (users), but retain their ID numbers in some cases. I frankly don't understand why you're willing to retain the fact that user 23332 logged in at 18:40 today, while not caring whether you can identify who user 23332 is.
You have a few options.
Drop the logfile table, and store logfile data in a file in the filesystem, not in the database. If I were in your shoes, I'd consider this first. If we're talking about a database that's somehow accessible over the web, make sure the log file is stored outside the web root. (I'd store it under /var/log with all the other log files.)
Use foreign key constraints, and never delete a user.
Use foreign key constraints, and live with the effect of ON DELETE SET NULL or ON DELETE SET DEFAULT. In this particular application ON DELETE SET NULL and ON DELETE SET DEFAULT are semantically equivalent. Both replace good data with data that doesn't identify the user. If you can't identify user 23332 anyway, who cares whether you know she logged in at 18:40 today?
Drop the foreign key constraints, and use triggers to do whatever you like.
I'm pretty sure we agree that the most obvious option--use foreign keys with ON DELETE CASCADE--is simply wrong for your application.

Related

How to execute a MySQL bulk insert with foreign keys and set to NULL if invalid

I'm importing several hundred thousands of rows coming from a service's xml data that has foreign keys pointing from screenings.venue_id to the venues table.
However I found out that some of their data has missing venues so bulk insert fails.
Is there a way to automatically set screenings.venue_id to NULL if the foreign key fails? I would really like to keep the foreign key for data cohesion and cascade update/delete without using triggers.
So far my best idea is to disable the foreign key checks before insert. This would work for initial import however this means that every time a screening is saved and has an invalid foreign key, it would fail.
For Subsequent inserts I would try this way..
Create a separate landing table which is doesn't have FK relation.
Create a Before insert Trigger on that landing table which verifies whether the Venue_ID exists on Venue Master table. If it exists then insert will continue if not you can skip the insert or insert those bad rows in another log table for future reference.
Hope this helps

how to repair data so foreign keys can be added back to database

I have just discovered my database which has been moved between a few servers in the last few months was, in one of the database dumps, converted to myisam and all the foreign key constraints nuked (not impressed). I've converted all the tables back to innodb with a php script but now I'm finding half the constraints are failing to add.
Error Code: 1452. Cannot add or update a child row: a foreign key constraint fails (`identicar2`.<result 2 when explaining filename '#sql-2a7_c0'>, CONSTRAINT `#sql-2a7_c0_ibfk_1` FOREIGN KEY (`feature_sk`) REFERENCES `tbl_feature_list` (`feature_sk`))
I'm lost now, I can't go through thousands of records manually. what is the best thing to do in order to get my database back into shape?
I can provide table structures if you want but I imagine any solution will be independant of the exact structure anyway
The simplest solution: (containing the obvious danger of losing data you may want to keep)
UPDATE table
SET fkColumn = NULL
WHERE fkColumn NOT IN (SELECT referenceColumn FROM referencedTable)
If you just want to find them:
SELECT *
FROM table
WHERE fkColumn NOT IN (SELECT referenceColumn FROM referencedTable)

mysql circular dependency in foreign key constraints

Given the schema:
What I need is having every user_identities.belongs_to reference an users.id.
At the same time, every users has a primary_identity as shown in the picture.
However when I try to add this reference with ON DELETE NO ACTION ON UPDATE NO ACTION, MySQL says
#1452 - Cannot add or update a child row: a foreign key constraint fails (yap.#sql-a3b_1bf, CONSTRAINT #sql-a3b_1bf_ibfk_1 FOREIGN KEY (belongs_to) REFERENCES users (id) ON DELETE NO ACTION ON UPDATE NO ACTION)
I suspect this is due to the circular dependency, but how could I solve it (and maintain referential integrity)?
The only way to solve this (at least with the limited capabilities of MySQL) to allow NULL values in both FK columns. Creating a new user with a primary identity would then look something like this:
insert into users (id, primary_identity)
values (1, null);
insert into identities (id, name, belongs_to)
values (1, 'foobar', 1);
update users
set primary_identity = 1
where id = 1;
commit;
The only drawback of this solution is that you cannot force that a user has a primary identity (because the column needs to be nullable).
Another option would be to change to a DBMS that supports deferred constraints, then you can just insert the two rows and the constraint will only be checked at commit time. Or use a DBMS where you can have a partial index, then you could use the solution with an is_primary column
I would not implement it this way.
Remove the field primary_identity from table users, and the add an additional field to table user_profiles called is_primary, and use this rather as the indicator of a primary profile
This will prevent having NULLs for FKs, but still does not enforce for primary profile to exists -- that has to be managed by application.
Note the alternate key (unique index) {UserID, ProfileID} on Profile table and matching FK on PrimaryProfile.
The problem seems to be that you are trying to keep the primary identity information in the user_identities table.
Instead, I suggest you put the primary user info (name/email) into the users table. Do not foreign key to the user_identities table.
Only foreign key from the user_identities table
All constraints will now work ok as they are only one way.
user_identities cannot be entered unless the primary user (in table users) is present. Similarly the primary user should not be deletable where there are existing child identities (in user_identities).
You might want to change the name of the tables to "primary_users" and "secondary_users" to make it obvious what is going on.
Does that sound okay?
This question was raised at How to drop tables with cyclic foreign keys in MySQL from the delete side of things, but I think that one of the answers is applicable here as well:
SET foreign_key_checks = 0;
INSERT <user>
INSERT <user identity>
SET foreign_key_checks = 1;
Make that a transaction and commit it all at once. I haven't tried it, but it works for deletes, so I don't know why it wouldn't work for inserts.
I've not used it, but you could try INSERT IGNORE. I'd do the two of those, one for each table, such that once they are both done, referential integrity is maintaing. If you do them in a transaction, you can roll back if there is a problem inserting the second one.
Since you're ignoring constraints with this feature, you should do that check in program code instead, otherwise you may end up with data in your database that ignores your constraints.
Thanks to #Mihai for pointing out the problem with the above. Another approach would be to disable constraints whilst you do inserts, and re-enable them afterwards. However, on a large table that might produce more overhead than is acceptable - try it?

delete 1 record from a table and automatically delete all records associated with the user_id in mysql

I have been trying to learn from reading tutorials online and stuff but I just can't put my finger on it.
I have 2 tables at the moment (i'll have a lot more later on as I build my application) so I want to knock out this issue before expanding and coding.
I have my tables set to use InnoDB and I have each table related to each other by using user_id as foreign keys.
If i issue a DELETE query on the main users table, how can i get all records from other tables that are linked to the user_id field get deleted as well?
I know its simple, but I think I just need to ask the question myself so I can understand the answer rather than reading the answer... heh
thank a lot for any help.
Since they are InnoDB tables with proper FK relationships, you can simply use ON DELETE CASCADE in the foreign key definition. For example in one of the related tables:
FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE
However, this requires altering your existing schema to modify the foreign keys.
See the MySQL FOREIGN KEY docs for complete information.
You can cascade the DELETE using a DELETE trigger or using the technique Michael suggested, or you can manually write enough DELETE statements (deleting the entities from the bottom of the hierarchy upwards so you don't violate the foreign key constraints) though the latter is clearly not an ideal solution in terms of maintenance.
You may wish to use the TRIGGER approach if you want to fine tune the delete process (e.g. in case you don't want to destroy certain data related to the foreign key, or if you wanted to move it elsewhere or associate it to a different ID).
this is mysql example
ALTER TABLE table_with_foregin_key ADD FOREIGN KEY ( foreign_key_column )
REFERENCES table_name (
user_id
) ON DELETE CASCADE ON UPDATE CASCADE;

Foreign key constraints: When to use ON UPDATE and ON DELETE

I'm designing my database schema using MySQL Workbench, which is pretty cool because you can do diagrams and it converts them :P
Anyways, I've decided to use InnoDB because of it's Foreign Key support. One thing I noticed though is that it allows you to set On Update and on Delete options for foreign keys. Can someone explain where "Restrict", "Cascade" and set null could be used in a simple example?
For example, say I have a user table which includes a userID. And say I have a message table message which is a many-to-many which has 2 foreign keys (which reference the same primary key, userID in the user table). Is setting the On Update and On Delete options any useful in this case? If so, which one do I choose? If this isn't a good example, could you please come up with a good example to illustrate how these could be useful?
Thanks
Do not hesitate to put constraints on the database. You'll be sure to have a consistent database, and that's one of the good reasons to use a database. Especially if you have several applications requesting it (or just one application but with a direct mode and a batch mode using different sources).
With MySQL you do not have advanced constraints like you would have in postgreSQL but at least the foreign key constraints are quite advanced.
We'll take an example, a company table with a user table containing people from theses company
CREATE TABLE COMPANY (
company_id INT NOT NULL,
company_name VARCHAR(50),
PRIMARY KEY (company_id)
) ENGINE=INNODB;
CREATE TABLE USER (
user_id INT,
user_name VARCHAR(50),
company_id INT,
INDEX company_id_idx (company_id),
FOREIGN KEY (company_id) REFERENCES COMPANY (company_id) ON...
) ENGINE=INNODB;
Let's look at the ON UPDATE clause:
ON UPDATE RESTRICT : the default : if you try to update a company_id in table COMPANY the engine will reject the operation if one USER at least links on this company.
ON UPDATE NO ACTION : same as RESTRICT.
ON UPDATE CASCADE : the best one usually : if you update a company_id in a row of table COMPANY the engine will update it accordingly on all USER rows referencing this COMPANY (but no triggers activated on USER table, warning). The engine will track the changes for you, it's good.
ON UPDATE SET NULL : if you update a company_id in a row of table COMPANY the engine will set related USERs company_id to NULL (should be available in USER company_id field). I cannot see any interesting thing to do with that on an update, but I may be wrong.
And now on the ON DELETE side:
ON DELETE RESTRICT : the default : if you try to delete a company_id Id in table COMPANY the engine will reject the operation if one USER at least links on this company, can save your life.
ON DELETE NO ACTION : same as RESTRICT
ON DELETE CASCADE : dangerous : if you delete a company row in table COMPANY the engine will delete as well the related USERs. This is dangerous but can be used to make automatic cleanups on secondary tables (so it can be something you want, but quite certainly not for a COMPANY<->USER example)
ON DELETE SET NULL : handful : if you delete a COMPANY row the related USERs will automatically have the relationship to NULL. If Null is your value for users with no company this can be a good behavior, for example maybe you need to keep the users in your application, as authors of some content, but removing the company is not a problem for you.
usually my default is: ON DELETE RESTRICT ON UPDATE CASCADE. with some ON DELETE CASCADE for track tables (logs--not all logs--, things like that) and ON DELETE SET NULL when the master table is a 'simple attribute' for the table containing the foreign key, like a JOB table for the USER table.
Edit
It's been a long time since I wrote that. Now I think I should add one important warning. MySQL has one big documented limitation with cascades. Cascades are not firing triggers. So if you were over confident enough in that engine to use triggers you should avoid cascades constraints.
http://dev.mysql.com/doc/refman/5.6/en/triggers.html
MySQL triggers activate only for changes made to tables by SQL statements. They do not activate for changes in views, nor by changes to tables made by APIs that do not transmit SQL statements to the MySQL Server
http://dev.mysql.com/doc/refman/5.6/en/stored-program-restrictions.html#stored-routines-trigger-restrictions
==> See below the last edit, things are moving on this domain
Triggers are not activated by foreign key actions.
And I do not think this will get fixed one day. Foreign key constraints are managed by the InnoDb storage and Triggers are managed by the MySQL SQL engine. Both are separated. Innodb is the only storage with constraint management, maybe they'll add triggers directly in the storage engine one day, maybe not.
But I have my own opinion on which element you should choose between the poor trigger implementation and the very useful foreign keys constraints support. And once you'll get used to database consistency you'll love PostgreSQL.
12/2017-Updating this Edit about MySQL:
as stated by #IstiaqueAhmed in the comments, the situation has changed on this subject. So follow the link and check the real up-to-date situation (which may change again in the future).
You'll need to consider this in context of the application. In general, you should design an application, not a database (the database simply being part of the application).
Consider how your application should respond to various cases.
The default action is to restrict (i.e. not permit) the operation, which is normally what you want as it prevents stupid programming errors. However, on DELETE CASCADE can also be useful. It really depends on your application and how you intend to delete particular objects.
Personally, I'd use InnoDB because it doesn't trash your data (c.f. MyISAM, which does), rather than because it has FK constraints.
Addition to #MarkR answer - one thing to note would be that many PHP frameworks with ORMs would not recognize or use advanced DB setup (foreign keys, cascading delete, unique constraints), and this may result in unexpected behaviour.
For example if you delete a record using ORM, and your DELETE CASCADE will delete records in related tables, ORM's attempt to delete these related records (often automatic) will result in error.