In SQLAlchemy is it possible to prevent (or specifically raise a custom Exception) the deletion of an object that is part of a collection in a many-to-many relationship.
For example below I have a Party and Person. Any Party can have many Persons and any Person can go to many Parties.
If a Person is recorded as going to one or more Parties I want to prohibit that Person from being deleted.
party_person_ass_table = db.Table('party_person', db.Model.metadata,
db.Column('party_id', db.ForeignKey('party.id'), primary_key=True),
db.Column('person_id', db.ForeignKey('person.id'), primary_key=True))
class Party(db.Model):
...
people = db.relationship('Person', secondary=party_person_ass_table, back_populates='parties')
class Person(db.Model):
...
parties = db.relationship('Party', secondary=party_person_ass_table, back_populates='people')
Reading the docs it seems cascade deleting from the secondary table is automatically set (https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html). I wonder if a solution to generate an IntegrityError would be to add a nullable=False to the person_id column in the secondary association table and then somehow prevent a cascade delete from impacting the secondary table?
adding the tag viewonly=True to:
parties = db.relationship('Party', secondary=party_person_ass_table, back_populates='people', viewonly=True)
decouples Person from the party_person_ass_table and when modified it does not automatically delete the row in the association table, which breaks database integrity.
This is also why the commented 'ON DELETE RESTRICT' fails because it isnt an inherent cascade, but SQLAlchemy specifically deletes these rows as part of its function so these hooks aren't called in the usual way.
Related
Let's say my database is for ecommerce store. Database holds records of users and orders. I have 2 tables 'users' and 'orders'.
'orders' table have userId column(foreign key) that reference to 'users' table id column.
When I want to delete a user- it throws an error because the 'orders' table have a record referencing this user's id. So how should I handle this?
I found 3 ways to overcome this.
not use foreign keys
use 'ON DELETE CASCADE' so when i delete user from 'users' table it will delete related order records of that user automaticaly.(is it a good idea?)
delete all child records and then delete parent record.
What is the best way?
For the simple question of how do you delete a record and it's references...
Foreign keys are critical for the integrity of your database. Without foreign keys you easily wind up with records that refer to objects which no longer exists. Don't remove them.
Manually deleting the referencing rows is error prone and will break when you change the schema.
on delete cascade allows you to have referential integrity, and also be able to delete records.
The comments got into the larger question of whether deleting user and order records are a good idea. There is not enough information in your question to know what is best. That would be another question.
However, even if you decide to use a status field to set users and orders as inactive (a timestamp, not a flag, because you'll want to know when the user was deactivated), you still want to set up the tables with on delete cascade so when you eventually do delete inactive records (for example, perhaps an annual cleanup, or for testing, or due to a mistake) the delete will work.
To start off, we have the following scenario (not exactly like this but for confidential reason I will not take the risk to explicitly describe the project) where let's say A person can Have Many home addresses and obviously an address belong to at most one person. This is pretty much a one to many relationship where there are 2 tables (Person and Addresses) with the second one holding a FK referencing the owner. But sadly i was told that my system should also allow users to enter Addresses first, so a FK that does not exist in the Person table yet. I came to the conclusion that i just have to drop that constraint. Do you thing it's a common thing to do? Furthermore I thought to maintain consistency even without FK (declared explicitly), if someone update the Person ID, since i dont want the user to do it in both table, is it possible to create a Trigger (I'm using Mysql server) that will update the second table automatically (if that person has an address)? If yes, any hint to how to write that (I'm not familiar with triggers)
You can maintain the 1:N relationship by creating the kind of intermediate table normally associated with M:N relationships but adding a uniqueness constraint on the address referencing field; alternatively, you can just make address' reference to person nullable, making it an optional reference.
[Edit]
Scripts from MySQL dump wrap their contents with something like:
SET #OLD_FOREIGN_KEY_CHECKS=##FOREIGN_KEY_CHECKS;
SET FOREIGN_KEY_CHECKS=0;
....
SET FOREIGN_KEY_CHECKS=#OLD_FOREIGN_KEY_CHECKS;
As you can probably guess, it temporarily turns off foreign key checks to allow inserting interdependent data out of order. However, you should be sure of the trustworthiness of the import data as I believe it does not recheck the data when turned back on (so it can allow data that violates FK constraints to persist).
If you need the ability to add "address" before "person" with user entered data, then you are back the original situation of needing optional keys or another table; or just not actually saving the "address" info (but instead holding it locally in the data entry program) until after the "person" info is sufficient/complete.
I have two classes
Account
id.
....
Invoice
id...
....
account_id (many to one)
As many invoices exist for one user, and it requires a User, I wrote it as many to one in invoice.
Cascading I believe means if I delete an Invoice then it will affect the account, by trying to add update/delete etc.
However to delete an account there are foreign key which means it cannot be deleted, unless I remove all invoices first.
A workaround is having a one to many/joining table in account, but I would rather have many to one as a invoice requires an account.
Is there a setting i could put in the orm to mean:
If I delete a Invoice nothing happens to account.
If I delete an account ALL the invoices linked to it are deleted
currently::
<many-to-one name="account" fetch="EAGER"
target-entity="UserAccount">
<join-column name="account_id"/>
<cascade><cascade-all/></cascade>
</many-to-one>
Yes. Map a OneToMany in Account to Invoice. You are mistaken when you thought it needed a join table - it does not. Based on you wanting to have a Invoice->Account ManyToOne relationship, you can just reuse this relationship:
#Entity
Class Account {
#OneToMany(cascade=ALL, mappedBy="account")
List<Invoice> invoices;
}
#Entity
Class Invoice{
#ManyToOne
Account account;
}
These settings mean the if you delete an Account, JPA will delete all referenced invoices as well. Since there are no cascade settings on the Invoice.account relation, deleting an Invoice does not affect the account. That said, you should remove the invoice from the Account.invoice collection when you are deleting an invoice instance. JPA does not maintain bidirectional relationships for you, so when you modify one side of a bidirectional relationhip, you are required to modify the other side as well to keep your object model in sync with the database - JPA allows for caching which won't reflect the database if you don't.
There are many tutorials you could look at for this relationship, but the simplest explaination I've found is:
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Mapping/Relationship_Mappings/Collection_Mappings/OneToMany
If you don't want it to be Bidirectional (ie you don't want the Invoice.account reference), I believe JPA 2.0 allows you to use a #JoinColumn annotation on the OneToMany:
#Entity
Class Account {
#OneToMany(cascade=ALL)
#JoinColumn(name="account_id")
List<Invoice> invoices;
}
One thing that I can suggest is to set cascading delete in the foreign key relationship in the database. That way when an account is deleted all the related invoices will be deleted easily.
I am using the Eloquent plymorphic relations and the ON DELETE CASCADE option of mysql, and am afraid of how my data will stay consistent over time. Let me explain:
If I have a model Room which contains furnitures of several types (morphMany of Table, Chair, Couch...), and that I decide to delete it, his related furnitures won't get deleted.
To solve this issure I handled the deletion of the related furnitures in the delete event of the Room model, but this event won't necessarily get fired all the time.
Let's say that I have an other model called House, containing several rooms (hasMany). If I decide to delete it, its related models will deleted by mysql with the ON DELETE CASCADE option. I will then stay with data in my tables tables, chairs, couchs that are not relevant anymore, and not related to any row of the rooms table anymore.
So what is the best way to keep a database consistent with ON DELETE CASCADE and the Eloquent's polymorphic relations?
If you're using the raw database (cascading) approach, that bypasses Eloquent and it becomes impossible to do what you want.
Set up a deleted event for House which iterates through all its Rooms and deletes those, which again triggers the Room's deleted events.
Why can't I just leave those relationships out?
What's the point of them?
I can stil run queries and treat them like it a relationship myself...
Yes, you can always leave the foreign key constraints out but then you will be responsible about the integrity of your data. If you use foreign key constraints, then you won't have to worry about the referential integrity among tables. You can read more about referential integrity from Wikipedia. I will also try to explain it with an example below.
Think of a shopping cart scenario. You have three tables: item, shopping_cart and shopping_cart_item. You can choose not to define any relationship between these tables, that's fine for any SQL solution. When user starts shopping, you create a shopping cart by adding a shopping_cart entry. As user adds items to his shopping cart, you save this information by adding rows to shopping_cart_item table.
One problem may occur at this step: If you have a buggy code that assigns incorrect shopping_cart_id's to shopping_cart_items, then you will definitely end up with incorrect data! Yes, you can have this case even with a foreign key constraint if the assigned id actually exists in the shopping_cart table. But this error will be more detectable when a foreign key exists since it would not insert shopping_cart_item record when the foreign key constraint fails.
Let's continue with the assumption that your code is not buggy and you won't have first type of referential integrity. Then suddenly a user wants to stop shopping and delete the cart and you chose to implement this case by deleting the shopping_cart and shopping_cart_item entries. Then you will have to delete entries in both tables with two separate queries. If something goes wrong after you delete shopping_cart entries, then you will again have a referential integrity problem: You will have shopping_cart_items that are not related to any shopping_cart. You will then have to introduce transaction managing, try to provide meaningful data to your business logic about the error happened in data access layer, etc..
In this type of scenario's, foreign keys can save life. You can define a foreign key constraint that will prevent insertion of any sort of incorrect data and you can define cascade operations that will automatically perform deletion of related data.
If there is anything unclear, just leave a comment and I can improve the answer.
Apart from what the others have said about why you technically want (actually: need) them:
foreign key constraints also document your model.
When looking at a model without FK constraints you have no idea which table relates to which. But with FK constraints in place you immediately see how things belong together.
You create FOREIGN KEYs to instruct the database engine to ensure that you never perform an action on the database that creates invalid records.
So, if you create a FOREIGN KEY relationship between users.id and visits.userid the engine will refuse to perform any actions that result in a userid value in visits that does not exist in users. This might be adding an unknown userid to visits, removing an id from users that already exists in visits, or updating either field to "break" the relationship.
That is why PRIMARY and FOREIGN KEYs are referred to as referential integrity constraints. The tell your database engine how to keep your data correct.
It doesn't allow you to enter an id which does not exist in another table, for example, if you have products and you keep owner Id, by creating a foreign key ton the owner id to id field of the owners table, you do not allow users to create an object record which has an owner id which does not exist in the owner table. such things are called referential intergrity.
The foreign key constraint helps you ensure referential integrity.
If you delete a row in one table, mysql can automatically delete all rows in other tables that the deleted row refers to via the foreign key. You can also make it reject the delete command.
Also when you try to insert a row, mysql can automatically create new rows in other tables, so the foreign key does not refer to nothing.
That is what referential integrity is all about.
Databases can be affected by more than just the application. Not all data changes go through the application even if they are supposed to. People change stuff directly on the database all the time. Rules that need to apply to all data all the time belong on the database. Suppose you can update the prices of your stock. That's great for updating anindividual price. But what happens when the boss decides to raise all prices by 15%. No one is going to go through and change 10,000 prices one at a time through the GUI, they are going to write a quick SQL script to do the update. Or suppose two suppliers join together to have one company and you want to change all of thie items to be the new company. Those kinds of changes happen to databases every day and they too need to follow the rules for data integrity.
New developers may not know about all the places where the foreign key relationships should exist and thus make mistakes which cause the data to be no longer useful.
Databases without foreign key constraints have close to a 100% chance of having bad data in them. Do you really want to have orders where you can't identify who the customers were?
THe FKS will prevent you from deleting a customer who has orders for instance or if you use a natural key of company_name and the name changes, all related records must be changed with the key change.
Or suppose you decide to put a new GUI together and dump the old one, then you might have to figure out all the FK relationships again (because you are using a different datalayer or ORM) and the chances are you might miss some.
It is irresponsible in the extreme to not put in FK relationships. You are risking the lifeblood of your company's business because you think it is a pain to do. I'd fire you if you suggested not using FKs because I would know I couldn't trust my company's data to you.