Mapped selects, nice! I wish I'd seen that part in the documentation
earlier. Do you think it might be worth a reference near Alternatives to
column_property()<http://www.sqlalchemy.org/docs/orm/mapper_config.html#alternatives-to-column-property>where
you also mention object_session()?

So there's one more issue. Once there's a lot of customers and orders, this
query performs pretty poorly in MySQL (tried 5.5.14 Source and 5.0.92-50-log
Percona). To simplify, I'll use joinedload() in my example instead of
subqueryload().

>>> f = 
>>> s.query(Customer).filter_by(id=46).options(joinedload("_num_orders")).all()2011-09-11
>>>  03:04:33,598 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)2011-09-11 
>>> 03:04:33,599 INFO sqlalchemy.engine.base.Engine SELECT customer.id AS 
>>> customer_id, anon_1.customer_id AS anon_1_customer_id, anon_1.count AS 
>>> anon_1_count
FROM customer LEFT OUTER JOIN (SELECT `order`.customer_id AS
customer_id, count(`order`.id) AS count
FROM `order` GROUP BY `order`.customer_id) AS anon_1 ON customer.id =
anon_1.customer_id
WHERE customer.id = %(id_1)s2011-09-11 03:04:33,599 INFO
sqlalchemy.engine.base.Engine {'id_1': 46}


The EXPLAIN plan:

mysql> explain SELECT customer.id AS customer_id, anon_1.customer_id
AS anon_1_customer_id, anon_1.count AS anon_1_count  FROM customer
LEFT OUTER JOIN (SELECT `order`.customer_id AS customer_id,
count(`order`.id) AS count  FROM `order` GROUP BY `order`.customer_id)
AS anon_1 ON customer.id = anon_1.customer_id  WHERE customer.id = 46
\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: customer
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: PRIMARY
        table: <derived2>
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 11200
        Extra:
*************************** 3. row ***************************
           id: 2
  select_type: DERIVED
        table: order
         type: index
possible_keys: NULL
          key: customer_id
      key_len: 5
          ref: NULL
         rows: 28725
        Extra: Using index3 rows in set (0.04 sec)


It results in a huge derived table and a table scan over the order table.
Out of curiosity, I ran the same statement in PostgresSQL (9.0.4) and
noticed it works much better:

sumeet=# explain analyze SELECT customer.id AS customer_id,
anon_1.customer_id AS anon_1_customer_id, anon_1.count AS anon_1_count
FROM customer LEFT OUTER JOIN (SELECT "order".customer_id AS
customer_id, count("order".id) AS count FROM "order" GROUP BY
"order".customer_id) AS anon_1 ON customer.id = anon_1.customer_id
WHERE customer.id = 46;
                                                          QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------
 Nested Loop Left Join  (cost=0.00..508.06 rows=1 width=16) (actual
time=4.069..4.070 rows=1 loops=1)
   Join Filter: (customer.id = "order".customer_id)
   ->  Index Scan using customer_pkey on customer  (cost=0.00..8.27
rows=1 width=4) (actual time=0.017..0.018 rows=1 loops=1)
         Index Cond: (id = 46)
   ->  GroupAggregate  (cost=0.00..499.77 rows=1 width=8) (actual
time=4.046..4.046 rows=1 loops=1)
         ->  Seq Scan on "order"  (cost=0.00..499.75 rows=2 width=8)
(actual time=0.021..4.042 rows=3 loops=1)
               Filter: (customer_id = 46)
 Total runtime: 4.127 ms
(8 rows)


Not that it helps me any :) I'm on MySQL. I can dramatically improve the
query by adding a WHERE clause inside the subquery. I bolded where the
change is:

mysql> explain SELECT customer.id AS customer_id, anon_1.customer_id
AS anon_1_customer_id, anon_1.count AS anon_1_count  FROM customer
LEFT OUTER JOIN (SELECT `order`.customer_id AS customer_id,
count(`order`.id) AS count  FROM `order` *WHERE customer_id = 46*
GROUP BY `order`.customer_id) AS anon_1 ON customer.id =
anon_1.customer_id  WHERE customer.id = 46 \G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: customer
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: PRIMARY
        table: <derived2>
         type: system
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
        Extra:
*************************** 3. row ***************************
           id: 2
  select_type: DERIVED
        table: order
         type: ref
possible_keys: customer_id
          key: customer_id
      key_len: 5
          ref:
         rows: 3
        Extra: Using where; Using index3 rows in set (0.00 sec)


I tried adding `anon_1.customer_id = 46` to the outer query's WHERE clause
since I'm pretty sure that's easily expressed with SQLAlchemy, but it didn't
help at all. Would it be easy to keep OrderCount defined as a relationship,
but somehow add a filter to the mapped select at runtime? Seems like then,
I'd be all set.

Thank you for the help.

On Wed, Sep 7, 2011 at 2:39 PM, Michael Bayer <mike...@zzzcomputing.com>wrote:

>
> On Sep 7, 2011, at 5:09 PM, Sumeet Agarwal wrote:
>
> Still getting the hang of SQLAlchemy terminology.
>
> I didn't mean a simple deferred() column, but a deferred column_property()
> which defines a scalar select(). Like in the example here
> http://www.sqlalchemy.org/docs/orm/mapper_config.html#sql-expressions-as-mapped-attributes
> .
>
> My goal is to, instead of including a correlated subquery in the SELECT.
> The column_property looks like
>
>
> "My goal is to...", missing a verb there :).     If it were me, I'd think
> you're asking to unwrap the correlated subquery into a plain JOIN.   Since
> that works more efficiently.
>
>
> I'd rather do a separate query to load num_orders, rather than getting it
> lazily or by using undefer(). It almost feels like I might want to define
> num_orders as a relationship somehow? I dunno if what I would like to do
> maps cleanly to a SQLAlchemy pattern.
>
>
> Using relationship() is kind of the old school approach, but yup I think
> that actually allows you to get exactly subqueryload() for an attribute, so
> good idea !  a rare moment that SQLAlchemy amazes even me.   here's that
> since its faster for me to just illustrate
>
> from sqlalchemy import *
> from sqlalchemy.orm import *
> from sqlalchemy.ext.declarative import declarative_base
>
> Base= declarative_base()
>
> class Customer(Base):
>     __tablename__ = 'customer'
>     id = Column(Integer, primary_key=True)
>     orders = relationship("Order")
>
>     @property
>     def num_orders(self):
>         return self._num_orders.count if self._num_orders else 0
>
> class Order(Base):
>     __tablename__ = 'order'
>     id = Column(Integer, primary_key=True)
>     customer_id = Column(Integer, ForeignKey('customer.id'))
>
> class OrderCount(object):
>     pass
> order_counts = select([Order.customer_id,
> func.count(Order.id).label('count')]).\
>         group_by(Order.customer_id).alias()
>
> mapper(
>     OrderCount,
>     order_counts,
>     primary_key=order_counts.c.customer_id
> )
>
> Customer._num_orders = relationship(OrderCount, uselist=False)
>
> e = create_engine('sqlite://', echo=True)
>
> Base.metadata.create_all(e)
>
> s = Session(e)
> s.add_all([
>     Customer(orders=[Order(), Order(), Order()]),
>     Customer(orders=[Order(), Order()]),
>     Customer(orders=[]),
> ])
> s.commit()
>
> for c in s.query(Customer).options(subqueryload("_num_orders")):
>     print c.id, c.num_orders
>
>
>
>
>
>
>
>
>
>
>
> On Wed, Sep 7, 2011 at 7:51 AM, Michael Bayer <mike...@zzzcomputing.com>wrote:
>
>> For a deferred() itself, we don't have an option that does this.    Though
>> this is an unusual request.   If you definitely want the deferreds to load,
>> what's the issue having them render inline into the original query ?    The
>> advantage to "subqueryload" is primarily in that it loads multiple rows per
>> parent object efficiently, without re-fetching the full parent row many
>> times,  or needing to use a less efficient OUTER JOIN.   A deferred is
>> always one row per parent - and no join is needed anyway.
>>
>>
>>
>>
>> On Sep 6, 2011, at 6:49 PM, Sumeet Agarwal wrote:
>>
>> > I have a collection of deferred `ColumnProperty`s that I'd like to
>> > start loading in subsequent queries. I know you can get this to happen
>> > on the first access of a `ColumnProperty` that is deferred, but I
>> > wonder if there's any way to specify this in the query.
>> >
>> > For relationships, `joinedload[_all()]` has a counterpart
>> > `subqueryload[_all()]`. Is there nothing similar we can do for
>> > `ColumnProperty`?
>> >
>> > --
>> > 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.
>> >
>>
>> --
>> 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.
>>
>>
>
> --
> 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.
>
>
>  --
> 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.
>

-- 
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.

Reply via email to