我在我的Django应用程序中实现了搜索,允许通过多个字段进行搜索。这导致Django总是使用LEFT OUTER JOIN
,在我的情况下会产生错误的结果。但是,当我将从LEFT OUTER JOIN
生成的SQL更改为INNER JOIN
时,它会返回正确的结果。
我认为这与我下面代码中Q object
的方式有关。
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
)
在上面的代码中,如何确保使用INNER JOIN而不是LEFT OUTER JOIN?
答案 0 :(得分:0)
根据您的问题,您想要搜索多个字段。但是,遵循您的逻辑,返回在OR序列中找到的第一个结果,而不返回OR
序列中可能的后续匹配;请记住,OR
运算符会停止对结果进行评估。
要将OUTER LEFT JOIN
转换为INNER JOIN
,您需要AND
/ OR
Q
个搜索字段组合的对象排列(最佳) ?),或单独查询它们并在结果上进行交集(次优),或自己编写SQL(次优)。
PS:在编写用于Django的Datatables API包装器之前,我遇到过这个问题。
PS:我会考虑重构,并进一步评论你的代码get_filtered_queryset
;我花了几分钟的时间来绕过这里发生的事情。