SQLAlchemy ORM – Deleting Related Objects
It is easy to perform delete operation on a single table. All you have to do is to delete an object of the mapped class from a session and commit the action. However, delete operation on multiple related tables is little tricky.
In our sales.db database, Customer and Invoice classes are mapped to customer and invoice table with one to many type of relationship. We will try to delete Customer object and see the result.
As a quick reference, below are the definitions of Customer and Invoice classes −
from sqlalchemy import create_engine, ForeignKey, Column, Integer, String engine = create_engine(''sqlite:///sales.db'', echo = True) from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() from sqlalchemy.orm import relationship class Customer(Base): __tablename__ = ''customers'' id = Column(Integer, primary_key = True) name = Column(String) address = Column(String) email = Column(String) class Invoice(Base): __tablename__ = ''invoices'' id = Column(Integer, primary_key = True) custid = Column(Integer, ForeignKey(''customers.id'')) invno = Column(Integer) amount = Column(Integer) customer = relationship("Customer", back_populates = "invoices") Customer.invoices = relationship("Invoice", order_by = Invoice.id, back_populates = "customer")
We setup a session and obtain a Customer object by querying it with primary ID using the below program −
from sqlalchemy.orm import sessionmaker Session = sessionmaker(bind=engine) session = Session() x = session.query(Customer).get(2)
In our sample table, x.name happens to be ”Gopal Krishna”. Let us delete this x from the session and count the occurrence of this name.
session.delete(x) session.query(Customer).filter_by(name = ''Gopal Krishna'').count()
The resulting SQL expression will return 0.
SELECT count(*) AS count_1 FROM ( SELECT customers.id AS customers_id, customers.name AS customers_name, customers.address AS customers_address, customers.email AS customers_email FROM customers WHERE customers.name = ?) AS anon_1(''Gopal Krishna'',) 0
However, the related Invoice objects of x are still there. It can be verified by the following code −
session.query(Invoice).filter(Invoice.invno.in_([10,14])).count()
Here, 10 and 14 are invoice numbers belonging to customer Gopal Krishna. Result of the above query is 2, which means the related objects have not been deleted.
SELECT count(*) AS count_1 FROM ( SELECT invoices.id AS invoices_id, invoices.custid AS invoices_custid, invoices.invno AS invoices_invno, invoices.amount AS invoices_amount FROM invoices WHERE invoices.invno IN (?, ?)) AS anon_1(10, 14) 2
This is because SQLAlchemy doesn’t assume the deletion of cascade; we have to give a command to delete it.
To change the behavior, we configure cascade options on the User.addresses relationship. Let us close the ongoing session, use new declarative_base() and redeclare the User class, adding in the addresses relationship including the cascade configuration.
The cascade attribute in relationship function is a comma-separated list of cascade rules which determines how Session operations should be “cascaded” from parent to child. By default, it is False, which means that it is “save-update, merge”.
The available cascades are as follows −
- save-update
- merge
- expunge
- delete
- delete-orphan
- refresh-expire
Often used option is “all, delete-orphan” to indicate that related objects should follow along with the parent object in all cases, and be deleted when de-associated.
Hence redeclared Customer class is shown below −
class Customer(Base): __tablename__ = ''customers'' id = Column(Integer, primary_key = True) name = Column(String) address = Column(String) email = Column(String) invoices = relationship( "Invoice", order_by = Invoice.id, back_populates = "customer", cascade = "all, delete, delete-orphan" )
Let us delete the Customer with Gopal Krishna name using the below program and see the count of its related Invoice objects −
from sqlalchemy.orm import sessionmaker Session = sessionmaker(bind = engine) session = Session() x = session.query(Customer).get(2) session.delete(x) session.query(Customer).filter_by(name = ''Gopal Krishna'').count() session.query(Invoice).filter(Invoice.invno.in_([10,14])).count()
The count is now 0 with following SQL emitted by above script −
SELECT customers.id AS customers_id, customers.name AS customers_name, customers.address AS customers_address, customers.email AS customers_email FROM customers WHERE customers.id = ? (2,) SELECT invoices.id AS invoices_id, invoices.custid AS invoices_custid, invoices.invno AS invoices_invno, invoices.amount AS invoices_amount FROM invoices WHERE ? = invoices.custid ORDER BY invoices.id (2,) DELETE FROM invoices WHERE invoices.id = ? ((1,), (2,)) DELETE FROM customers WHERE customers.id = ? (2,) SELECT count(*) AS count_1 FROM ( SELECT customers.id AS customers_id, customers.name AS customers_name, customers.address AS customers_address, customers.email AS customers_email FROM customers WHERE customers.name = ?) AS anon_1(''Gopal Krishna'',) SELECT count(*) AS count_1 FROM ( SELECT invoices.id AS invoices_id, invoices.custid AS invoices_custid, invoices.invno AS invoices_invno, invoices.amount AS invoices_amount FROM invoices WHERE invoices.invno IN (?, ?)) AS anon_1(10, 14) 0