Eager Loading
Eager load reduces the number of queries. SQLAlchemy offers eager loading functions invoked via query options which give additional instructions to the Query. These options determine how to load various attributes via the Query.options() method.
Subquery Load
We want that Customers.invoices should load eagerly. The orm.subqueryload() option gives a second SELECT statement that fully loads the collections associated with the results just loaded. The name “subquery” causes the SELECT statement to be constructed directly via the Query re-used and embedded as a subquery into a SELECT against the related table.
- c1 = session.query(Customers).options(subqueryload(Customers.invoices)).filter_by(name='Gopal Krishna').one()
- print('Name of c1 is {}'.format(c1.name))
- print('Invoices of c1:')
- for iv in c1.invoices:
- print('\t{} (amount={:,d})'.format(iv.invno, iv.amount))
This emits following expression giving same output as above:
- 2019-06-27 09:58:27,452 INFO sqlalchemy.engine.base.Engine 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 = %(name_1)s
- 2019-06-27 09:58:27,452 INFO sqlalchemy.engine.base.Engine {'name_1': 'Gopal Krishna'}
- 2019-06-27 09:58:27,454 INFO sqlalchemy.engine.base.Engine SELECT invoices.id AS invoices_id, invoices.custid AS invoices_custid, invoices.invno AS invoices_invno, invoices.amount AS invoices_amount, anon_1.customers_id AS anon_1_customers_id
- FROM (SELECT customers.id AS customers_id
- FROM customers
- WHERE customers.name = %(name_1)s) AS anon_1 JOIN invoices ON anon_1.customers_id = invoices.custid ORDER BY anon_1.customers_id, invoices.id
- 2019-06-27 09:58:27,454 INFO sqlalchemy.engine.base.Engine {'name_1': 'Gopal Krishna'}
The other function is called orm.joinedload(). This emits a LEFT OUTER JOIN. Lead object as well as the related object or collection is loaded in one step:
- # Joined Load
- c1 = session.query(Customers).options(joinedload(Customers.invoices)).filter_by(name='Gopal Krishna').one()
- 2019-06-27 10:00:21,443 INFO sqlalchemy.engine.base.Engine SELECT customers.id AS customers_id, customers.name AS customers_name, customers.address AS customers_address, customers.email AS customers_email, invoices_1.id AS invoices_1_id, invoices_1.custid AS invoices_1_custid, invoices_1.invno AS invoices_1_invno, invoices_1.amount AS invoices_1_amount
- FROM customers LEFT OUTER JOIN invoices AS invoices_1 ON customers.id = invoices_1.custid
- WHERE customers.name = %(name_1)s ORDER BY invoices_1.id
- 2019-06-27 10:00:21,443 INFO sqlalchemy.engine.base.Engine {'name_1': 'Gopal Krishna'}
The subqueryload() is more appropriate for loading related collections while joinedload() is better suited for many-to-one relationship.
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 testdb database, Customers and Invoice classes are mapped to customer and invoice table with one to many type of relationship. We will try to delete Customers object and see the result.
As a quick reference, below are the definitions of Customers and Invoice classes:
- build_rel_ex1.py
- from sqlalchemy import Column, Integer, String, ForeignKey
- from sqlalchemy import create_engine
- from sqlalchemy.ext.declarative import declarative_base
- from sqlalchemy.orm.session import sessionmaker
- from sqlalchemy.orm import relationship
- db_string = "postgresql://postgres:john7810@localhost/testdb"
- engine = create_engine(db_string, echo=True)
- Base = declarative_base()
- class Customers(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("Customers", back_populates = "invoices")
- Customers.invoices = relationship("Invoice", order_by = Invoice.id, back_populates = "customer")
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:
However, the related Invoice objects of x are still there. It can be verified by the following:
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. 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 Customers.invoices 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
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:
- Customers.invoices = relationship("Invoice", order_by = Invoice.id, back_populates = "customer", cascade = 'all, delete, delete-orphan')
- testdb=# SELECT * FROM customers WHERE name='Gopal Krishna';
- id | name | address | email
- ----+---------------+-----------------------+--------------
- 5 | Gopal Krishna | Bank Street Hydarebad | gk@gmail.com
- (1 row)
- testdb=# SELECT * FROM Invoices WHERE custid = 5;
- id | custid | invno | amount
- ----+--------+-------+--------
- 1 | 5 | 10 | 15000
- 2 | 5 | 14 | 3850
- (2 rows)
- delete_gk.py
- #!/usr/bin/env python
- from build_rel_ex1 import *
- x = session.query(Customers).get(5)
- print('Delete customer={} (id={})'.format(x.name, x.id))
- cust_name = x.name
- cust_id = x.id
- session.delete(x)
- num_cust = session.query(Customers).filter_by(name = cust_name).count()
- print('Now we have {:,d} customer with name as {}...'.format(num_cust, cust_name))
- num_invo = session.query(Invoice).filter(Invoice.custid == x.id).count()
- print('Now we have {:,d} invoice associated with customer id={}!'.format(num_invo, cust_id))
Supplement
* Prev - SQLAlchemy ORM - Working with Related Objects, Joins and common operators
* Next - SQLAlchemy ORM - Many to Many Relationships
* SQLAlchemy Doc - Query API
* SQLAlchemy Doc - Relationship Loading Techniques
沒有留言:
張貼留言