On Dec 17, 2011, at 6:56 PM, Jad Kik wrote:

> Hi,
> My problem is with using a many-to-many relationship inside a query.
> 
> 
> I wanted to get all the Customers who are associated to a
> CustomerGroup, I did:
> session.query(Customer).filter(Customer.groups.contains(group))
> 
> I wanted to get all the Customers who do not belong to any
> CustomerGroup. I tried that:
> session.query(Customer).filter(len(Customer.groups) == 0)
> which threw an exception "TypeError: object of type
> 'InstrumentedAttribute' has no len()".
> I understand what that means, but it would be a good thing to have it
> available, something similar to Customer.groups.contains().
> 
> I can't seem to do such a thing neither:
> session.query(Customer).filter([some subquery].count() == 0)
> Then what would [some subquery] be?
> 
> I did manage to do it with normal SQL (on MySQL 5.1):
> SELECT c2.id FROM customers c2 WHERE (SELECT COUNT(cg.id) FROM
> customers c, customergroups cg, customer_group ccg WHERE
> ccg.customer_id=c.id AND ccg.group_id=cg.id AND c.id=c2.id)=0
> It worked as I expected.


The usual form of this query is relational terms (the set of all customers) 
<intersection> (the set of all customer ids in customer_group with N number of 
group id).   The second half of it uses GROUP BY to group by the customer id 
and HAVING to limit the rows, and you'd use join() to join the two together as 
in the form at 
http://www.sqlalchemy.org/docs/orm/tutorial.html#using-subqueries .

But here you're looking for the count being zero.   So a subquery against 
customer_group won't have any rows you'd care about.   To query for an 
exclusion, use WHERE NOT EXISTS.   In the ORM this is the expression 
~Customer.groups.any(), just like the third example at 
http://www.sqlalchemy.org/docs/orm/internals.html#sqlalchemy.orm.properties.RelationshipProperty.Comparator.any
 .   The join across the association table is handled here within the subquery.

The correlated subquery approach you have is more amenable to being "packaged 
up" as a simple WHERE criterion on the Python side, but you'll note the format 
of the query is less well suited to the typical optimizer.  MySQL's optimizer 
in particular is awful, so this is an example where len(SomeRelationship) might 
look tidy on the Python side but is actually not taking the reality of the 
relational database into account to an appropriate degree.    You could achieve 
that exact form by using the style at 
http://www.sqlalchemy.org/docs/orm/tutorial.html#using-subqueries in 
conjunction with aliased(Customer) to produce "c2".

I don't know if any of these ways is really "cleaner", except for the ~any() 
that is only appropriate for the "no groups" comparison.


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