More docs added
This commit is contained in:
@@ -1,23 +1,45 @@
|
|||||||
"""
|
"""
|
||||||
SQLAlchemy-Utils provides way of automatically calculating aggregate values of related models and saving them to parent model.
|
SQLAlchemy-Utils provides way of automatically calculating aggregate values of
|
||||||
|
related models and saving them to parent model.
|
||||||
|
|
||||||
This solution is inspired by RoR counter cache and especially counter_culture_.
|
This solution is inspired by RoR counter cache,
|
||||||
|
`counter_culture`_ and `stackoverflow reply by Michael Bayer`_.
|
||||||
|
|
||||||
|
Why?
|
||||||
|
----
|
||||||
|
|
||||||
|
Many times you may have situations where you need to calculate dynamically some
|
||||||
|
aggregate value for given model. Some simple examples include:
|
||||||
|
|
||||||
|
- Number of products in a catalog
|
||||||
|
- Average rating for movie
|
||||||
|
- Latest forum post
|
||||||
|
- Total price of orders for given customer
|
||||||
|
|
||||||
|
Now all these aggregates can be elegantly implemented with SQLAlchemy
|
||||||
|
column_property_ function. However when your data grows calculating these
|
||||||
|
values on the fly might start to hurt the performance of your application. The
|
||||||
|
more aggregates you are using the more performance penalty you get.
|
||||||
|
|
||||||
|
This module provides way of calculating these values automatically and
|
||||||
|
efficiently at the time of modification rather than on the fly.
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
.. _counter_culter:: https://github.com/magnusvk/counter_culture
|
* Automatically updates aggregate columns when aggregated values change
|
||||||
|
* Supports aggregate values through arbitrary number levels of relations
|
||||||
|
* Highly optimized: uses single query per transaction per aggregate column
|
||||||
|
* Aggregated columns can be of any data type and use any selectable scalar expression
|
||||||
|
|
||||||
|
|
||||||
Non-atomic implementation:
|
.. _column_property: http://docs.sqlalchemy.org/en/latest/orm/mapper_config.html#using-column-property
|
||||||
|
.. _counter_culture: https://github.com/magnusvk/counter_culture
|
||||||
|
.. _stackoverflow reply by Michael Bayer:
|
||||||
http://stackoverflow.com/questions/13693872/
|
http://stackoverflow.com/questions/13693872/
|
||||||
|
|
||||||
|
|
||||||
We should avoid deadlocks:
|
|
||||||
|
|
||||||
http://mina.naguib.ca/blog/2010/11/22/postgresql-foreign-key-deadlocks.html
|
|
||||||
|
|
||||||
|
|
||||||
Simple aggregates
|
Simple aggregates
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
@@ -84,11 +106,15 @@ Custom aggregate expressions
|
|||||||
id = sa.Column(sa.Integer, primary_key=True)
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
name = sa.Column(sa.Unicode(255))
|
name = sa.Column(sa.Unicode(255))
|
||||||
price = sa.Column(sa.Numeric)
|
price = sa.Column(sa.Numeric)
|
||||||
monthly_license_price = sa.Column(sa.Numeric)
|
|
||||||
|
|
||||||
catalog_id = sa.Column(sa.Integer, sa.ForeignKey(Catalog.id))
|
catalog_id = sa.Column(sa.Integer, sa.ForeignKey(Catalog.id))
|
||||||
|
|
||||||
|
|
||||||
|
Now the net_worth column of Catalog model will be automatically whenever:
|
||||||
|
|
||||||
|
* A new product is added to the catalog
|
||||||
|
* A product is deleted from the catalog
|
||||||
|
* The price of catalog product is changed
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
@@ -96,18 +122,95 @@ Custom aggregate expressions
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
|
product1 = Product(name='Some product', price=Decimal(1000))
|
||||||
|
product2 = Product(name='Some other product', price=Decimal(500))
|
||||||
|
|
||||||
|
|
||||||
catalog = Catalog(
|
catalog = Catalog(
|
||||||
name=u'My first catalog'
|
name=u'My first catalog'
|
||||||
products=[
|
products=[
|
||||||
Product(name='Some product', price=Decimal(1000)),
|
product1,
|
||||||
Product(name='Some other product', price=Decimal(500))
|
product2
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
session.add(catalog)
|
session.add(catalog)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
session.refresh(catalog)
|
||||||
catalog.net_worth # 1500
|
catalog.net_worth # 1500
|
||||||
|
|
||||||
|
session.delete(product2)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(catalog)
|
||||||
|
|
||||||
|
catalog.net_worth # 1000
|
||||||
|
|
||||||
|
product1.price = 2000
|
||||||
|
session.commit()
|
||||||
|
session.refresh(catalog)
|
||||||
|
|
||||||
|
catalog.net_worth # 2000
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Multi-level aggregates
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
|
||||||
|
from sqlalchemy_utils import aggregated_attr
|
||||||
|
|
||||||
|
|
||||||
|
class Catalog(Base):
|
||||||
|
__tablename__ = 'catalog'
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
name = sa.Column(sa.Unicode(255))
|
||||||
|
|
||||||
|
@aggregated_attr('categories.products')
|
||||||
|
def net_worth(self):
|
||||||
|
return sa.Column(sa.Integer)
|
||||||
|
|
||||||
|
@aggregated_attr.expression
|
||||||
|
def net_worth(self):
|
||||||
|
return sa.func.sum(Product.price)
|
||||||
|
|
||||||
|
|
||||||
|
categories = sa.orm.relationship('Product')
|
||||||
|
|
||||||
|
|
||||||
|
class Category(Base):
|
||||||
|
__tablename__ = 'category'
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
name = sa.Column(sa.Unicode(255))
|
||||||
|
|
||||||
|
catalog_id = sa.Column(sa.Integer, sa.ForeignKey(Catalog.id))
|
||||||
|
|
||||||
|
products = sa.orm.relationship('Product')
|
||||||
|
|
||||||
|
|
||||||
|
class Product(Base):
|
||||||
|
__tablename__ = 'product'
|
||||||
|
id = sa.Column(sa.Integer, primary_key=True)
|
||||||
|
name = sa.Column(sa.Unicode(255))
|
||||||
|
price = sa.Column(sa.Numeric)
|
||||||
|
|
||||||
|
category_id = sa.Column(sa.Integer, sa.ForeignKey(Category.id))
|
||||||
|
|
||||||
|
|
||||||
|
TODO
|
||||||
|
----
|
||||||
|
|
||||||
|
* Special consideration should be given to `deadlocks`_.
|
||||||
|
|
||||||
|
|
||||||
|
.. _deadlocks:
|
||||||
|
http://mina.naguib.ca/blog/2010/11/22/postgresql-foreign-key-deadlocks.html
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user