On Nov 21, 2008, at 11:46 PM, Malcolm Tredinnick wrote:
> > > On Fri, 2008-11-21 at 22:37 -0500, Michael Keselman wrote: >> Hello, >> >> http://pastie.org/private/xvqf5tgjnimiolakhawgg (relevant code) >> >> Django's QuerySets allow you to do ORs (unions) pretty nicely with >> Model.manager.filter(Q(...) | Q(...)). However, ANDs don't work quite >> as nicely in my situation because I want AND to do what >> Model.manager.filter(Q(...)).filter(Q(...)) would do, but >> Model.manager.filter(Q(...) & Q(...)) does something very different. >> It does not compare fields with other rows in the table but rather >> with the same row against itself. For example, I have a Tag model >> with >> a CharField I call 'name'. If I were to do >> Tag.objects.filter(Q(name='text') & Q(name='password')), it would >> compare each tag to check if the tag's name equals "text" AND that >> the >> same tag's name equals "password". > > Yeah, this is the edge-case that isn't handled out of the box. On the > (likely) probability that it's much less common than the alternative > behaviour, we decided to go with the current approach. You use-case > isn't invalid, but it's never the goal to support every possible > use-case. > > Speaking as an implementor, I'll also note that it's the really, > really > hard case to make it work efficiently in all cases (particularly with > highly nested trees of ANDs and ORs), so it falls close to, or > possibly > over the line marked "out of scope for the ORM; custom SQL is your > friend". > > I can think of four(!) possible approaches for your problem. In rough > order from least intrusive to the one requiring the most changes (and > roughly least efficient to most efficient), we have the following. I > haven't written code for any of these, so I'm using English rather > than > Python. But my intuition is that they should all work. > > (1) After you have parsed the query, rewrite it in conjunctive normal > form (CNF). That's a fairly routine transformation. Then each clause > in > the CNF can be written as Q(...)|Q(...)|... and can be put in a > separate > call to filter() (so you end up with one filter() call per clause in > the > CNF). Guaranteed to give the right answer and requires no > understanding > of Django's internals. The downside is that more comparisons than are > possibly necessary will be made. Still, in practice, the difference > possibly won't be noticed on a well-index table. > > (2) Write your own variation on Q() -- let's call it Q1 here, for > brevity. This requires understanding what's going on when you call > filter() a bit, but since you are looking at a case that isn't > normally > handled, you need to roll up your sleeves and dive in. It's not that > bad, really. > > Basically, filter() wraps up its parameters in a Q() object and then > calls Query.add_q(). So calling Queryset.filter() is equivalent to an > outer call to Query.add_q(). The way a single call to add_q() knows > that > it can (and should) reuse certain aliases is the used_aliases > parameter > passed into add_q(). So you could force a particular alias not to be > reused if you adjusted used_aliases. Notice that used_aliases is also > passed to the add_to_query() method of any Q-like object you might > pass > in. > > In practice, that means you write a class Q1 that subclasses Q and > which > has an add_to_query() method. In your code that constructs the filter > call, you use Q1 when you are creating a conjunction of terms, rather > than Q -- for example, Q1(Q(name="a") & Q(name="b")). Your > Q1.add_to_query() method will then generally call Q.add_q(), except > that > after each return it will remove any newly added aliases from > used_aliases so that they aren't reused inside that Q1. You can do > this > by deep copying used_aliases before and restoring it afterwards. Just > before finally returning from your add_to_query() method, you might > want > to add back all the used aliases so that any outer calls can reuse > them. > It's this nested removing and readding of aliases that makes this a > hard > problem (I would strongly suggest experimenting with something like > (A & > (B|(C&D)) to make sure things work as expected). > > (3) You could write your own equivalent to add_q() and call that for > your case. Since filter() calls add_q(), subclass QuerySet and add > your > own disjoint_filter() method, say, that calls your own add_q() > method -- > which need not even be a separate method if you don't want to subclass > Query as well and don't mind calling setup_filter() directly. In your > own add_q() version, you might choose to not update used_aliases for > some or all situations. I think not updating used_aliases at all will > lead to very inefficient queries -- lots of unnecessary tables > involved > -- so this could well reduce to option (2), above, in which case I'd > go > with option (2). But have a think about it and see. > > (4) Since it looks like you're doing the equivalent of text searching > with boolean operators, my fourth solution would be to use a text > searching tool. Solr or Sphinx or something like that which indexes > the > tag field in question. The reason for suggesting this option is that > most of those search engines have built-in support for boolean logic > combinators. It would require custom SQL to do the searching (or you > could look at the djangosearch project), but might well save a lot of > effort. > > Maybe that will give you some ideas. > > Regards, > Malcolm > > > > Thank you so much for your reply, Malcolm! It turns out that with my code, the AND queries that I want kind of work. When I start to add parentheses, though, the way I handle AND queries creates in a completely different result than intended. I'll try solutions 1 and 4 and the custom SQL approach for a few hours/days and see which works better. Thanks again, Michael --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Django users" group. To post to this group, send email to django-users@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/django-users?hl=en -~----------~----~----~----~------~----~------~--~---