On Fri, Apr 27, 2012 at 02:22:11PM -0400, Michael Bayer wrote: > Ah, I wonder how the docs are giving that impression - column_property() is > often a poor choice, the only reasons one would want to use column_property() > are so that the expression is evaluated and placed inline into the main > SELECT query when the rest of the object is loaded from the database, and > additionally that the value is kept persistent once loaded and in sync with > the object's state, that is, when the object is expired the value here is > expired also. It also can be used as a filter expression in this role. > > The great limitation of column_property() is that it can only be emitted as a > column expression embedded into the columns clause of a SELECT on the parent > table, meaning any access it requires to related tables can only be achieved > using correlated subqueries, which is what you're seeing here. And actually > the query you're illustrating seems correct, too, I'd think you're getting > the right result back at least. If you're on PG I'd be curious what it > comes up with for EXPLAIN on that subquery, versus, the join, versus the > plain SELECT. The subquery/join versions might have the same execution plan > (or maybe not). > > Even though you're on 0.6, the more recent docs have been organized to be > more accurate as to the role and limitations of column_property() which are > worth reading at > http://docs.sqlalchemy.org/en/latest/orm/mapper_config.html#mapper-sql-expressions > . The @hybrid functionality discussed there is also available with 0.6 via > a simple recipe, though @hybrid isn't appropriate here since you definitely > need to hit the database for this value. > > Since you're using deferred, this indicates you want the SELECT to be emitted > on access, not within the main SELECT for account objects. So I'd definitely > go with @property here. If you want it to evaluate only once, I'd use a > memoized @property that caches the value inside of __dict__. If you need > the memoized value to expire when data changes elsewhere, then you might need > to use event hooks for that part of it, like the after_flush() event. I've > added an example for that here: > http://www.sqlalchemy.org/trac/wiki/UsageRecipes/ExpiryMemoized (uses 0.7 > events at the moment, 0.6 has SessionExtension). >
Thanks for the reply, Michael. Having re-read the docs I think my brain somehow twisted "In cases where a SQL query more elaborate than what orm.column_property() or hybrid_property can provide must be emitted, a regular Python function accessed as an attribute can be used, assuming the expression only needs to be available on an already-loaded instance."[0] into "orm.column_property() should be used in preference to functions mapped as attributes", which is clearly not what it says. [0] http://docs.sqlalchemy.org/en/latest/orm/mapper_config.html#using-a-plain-descriptor I am indeed using Postgresql, here's the results you asked for: -----8<-------------------->8----- EXPLAIN SELECT (SELECT sum(transaction_account_postings.value) AS sum_1 budget(> FROM transaction_account_postings budget(> WHERE accounts.id = transaction_account_postings.account_id) AS anon_1 budget-> FROM accounts budget-> WHERE accounts.id =1; QUERY PLAN --------------------------------------------------------------------------------------------- Index Scan using accounts_pkey on accounts (cost=0.00..57.51 rows=1 width=4) Index Cond: (id = 1) SubPlan 1 -> Aggregate (cost=49.23..49.24 rows=1 width=4) -> Seq Scan on transaction_account_postings (cost=0.00..48.12 rows=442 width=4) Filter: ($0 = account_id) (6 rows) -----8<-------------------->8----- -----8<-------------------->8----- EXPLAIN SELECT sum(transaction_account_postings.value) FROM transaction_account_postings INNER JOIN accounts ON transaction_account_postings.account_id = accounts.id WHERE accounts.id = 1; QUERY PLAN -------------------------------------------------------------------------------------------- Aggregate (cost=75.40..75.41 rows=1 width=4) -> Nested Loop (cost=0.00..71.59 rows=1520 width=4) -> Index Scan using accounts_pkey on accounts (cost=0.00..8.27 rows=1 width=4) Index Cond: (id = 1) -> Seq Scan on transaction_account_postings (cost=0.00..48.12 rows=1520 width=8) Filter: (transaction_account_postings.account_id = 1) (6 rows) -----8<-------------------->8----- -----8<-------------------->8----- EXPLAIN SELECT sum(transaction_account_postings.value) FROM transaction_account_postings WHERE account_id = 1; QUERY PLAN -------------------------------------------------------------------------------------- Aggregate (cost=51.93..51.94 rows=1 width=4) -> Seq Scan on transaction_account_postings (cost=0.00..48.12 rows=1520 width=4) Filter: (account_id = 1) (3 rows) -----8<-------------------->8----- This absolutely needs to be deferred and only hit the database on request as it is such an expensive operation which is only needed for a handful of front-end logic paths. I will use a property, which is probably what I should have done in the first place rather then try and make column_property do something it isn't really designed to do. Oh well, at least I've improved my understanding of column_property from this experience. Thanks again, Laurence -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com. To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.