Forcing Django to use INNER JOIN instead of LEFT OUTER JOIN

650 views
Skip to first unread message

Frankline

unread,
Jul 24, 2014, 8:20:34 AM7/24/14
to django...@googlegroups.com

I have implemented search in my Django application to allow searching by more than one field. This results in Django always using a LEFT OUTER JOIN, which in my case generates the wrong results. However, when I change the SQL generated from a LEFT OUTER JOIN to an INNER JOIN, it returns the correct result.

I am thinking it has to do with the way Q object in my code below.

from django.db import models, transaction
...
def construct_search(field_name):
    if field_name.startswith('^'):
        return "%s__istartswith" % field_name[1:]
    elif field_name.startswith('='):
        return "%s__iexact" % field_name[1:]
    elif field_name.startswith('@'):
        return "%s__search" % field_name[1:]
    else:
        return "%s__icontains" % field_name


class CoreSearchMixin(object):
    """Subclasses must define search_fields = [field_1, ...field_n]
    where the field is a string, the name of a field, and can contain the following prefix characters:

    '^': the search field must start with the search term, case insensitive
    '=': the search field must exactly equal the search term, case insensitive
    '@': full-text search

    If no prefix is given, any string that contains the search field will match.
    """
    search_fields = None
    search_form_class = SearchForm

    @cachedproperty
    def search_form(self):
        return self.search_form_class(getattr(self.request, self.request.method))

    def get_query_help_message(self):
        """Returns a comma separated list of fields that are used in the search, to help the user
        create a search.
        """
        fields = []
        if self.search_fields:
            for search_field in self.search_fields:
                field = get_field_from_path(self.model, search_field)
                fields.append(field.verbose_name.title())
        return ",".join(fields)

    def get_filtered_queryset(self, queryset):
        if self.search_form.is_valid():
            self.query = self.search_form.cleaned_data['q']
        else:
            self.query = None
        if self.search_fields and self.query:
            orm_lookups = (construct_search(str(search_field).replace('.', '__'))
                           for search_field in self.search_fields)
            chained_or_queries = None
            for bit in self.query.split():
                or_queries = (models.Q(**{orm_lookup: bit})
                              for orm_lookup in orm_lookups)
                if chained_or_queries:
                    chained_or_queries = itertools.chain(chained_or_queries, or_queries)
                else:
                    chained_or_queries = or_queries
            return queryset.filter(reduce(operator.or_, chained_or_queries))
        else:
            return queryset

    def get_context_data(self, **kwargs):
        return super(CoreSearchMixin, self).get_context_data(
            search_form=self.search_form,
            query_help_message=self.get_query_help_message(),
            search_fields=self.search_fields,
            **kwargs
        )

How can I ensure that an INNER JOIN is used instead of a LEFT OUTER JOIN in the case of my code above?


http://stackoverflow.com/questions/24927062/forcing-django-to-use-inner-join-instead-of-left-outer-join

Tom Evans

unread,
Jul 24, 2014, 9:15:12 AM7/24/14
to django...@googlegroups.com
Can you reduce that code to a single line test case please.

Eg, "Foo.objects.filter(Q(...) | Q(...)) is making a LEFT OUTER JOIN, why"

I suspect that you are OR'ing lookups that span the same relationship.

Cheers

Tom
Reply all
Reply to author
Forward
0 new messages