如何编写在内部联接中使用复杂的“ on”子句的Django查询?

时间:2019-03-21 02:11:16

标签: python django python-3.x postgresql inner-join

我正在使用Django,Python 3.7和PostgreSQL 9.5。我有这些模型:

class Article(models.Model):
    ...
    label = models.TextField(default='', null=True)


class Label(models.Model):
    name = models.CharField(max_length=200)

我想编写一个Django查询,以检索标签中包含标签名称的所有文章。在PostGres中,我可以这样构造查询:

select a.* from myapp_article a join myapp_label l on a.label ilike '%' || l.name || '%';

但是由于“ on”子句和“ ilike”,我不知道如何在Django中实现这一点。我该怎么拉?

3 个答案:

答案 0 :(得分:1)

如果您必须在Article的标签上进行不区分大小写的搜索以查找匹配的名称,则可以使用regex并将其传递给所有标签名称的统一列表,如下所示:

Article.objects.filter(label__iregex=r'(' + '|'.join(Label.objects.all().values_list('name', flat=True)) + ')')

上面的查询所做的是,它使标签的列表平坦:

['label1' , 'label2', 'label3']

,然后像这样将字符串连接起来:

'(label1|label2|label3)'

并使用类似的SQL查询:

SELECT * from FROM "app_article" WHERE "app_article"."label" ~* (label1|label2|label3)

否则,对于区分大小写的方法,可以使用以下方法:

names_list = Label.objects.all().values_list('name', flat=True)
Article.objects.filter(label__in=names_list)

答案 1 :(得分:0)

在您的class Article中,您必须将标签声明为class Label的外键

class Article(models.Model):
    ...
    label = models.ForeignKey(Label, default='', on_delete=models.CASCADE)

然后您可以访问它。

答案 2 :(得分:0)

这不会转换为相同的SQL查询,但是使用内部查询会产生相同的结果。

inner_query = Label.objects.annotate(article_label=OuterRef('label')).filter(article_label__icontains=F('name'))
articles = Article.objects.annotate(labels=Subquery(inner_query.values('name')[:1])).filter(labels__isnull=False)

这应该大致翻译为:

select a.* from myapp_article a where exists (select l.* from myapp_label l where a.label ilike '%' || l.name || '%')

但是,由于Django当前有关于在注解中使用OuterRef的issue,这种方法行不通。我们需要使用建议的here解决方法,直到解决该问题以使此查询正常工作为止,如下所示:

首先定义自定义表达式

class RawCol(Expression):

    def __init__(self, model, field_name):
        field = model._meta.get_field(field_name)
        self.table = model._meta.db_table
        self.column = field.column
        super().__init__(output_field=CharField())

    def as_sql(self, compiler, connection):
        sql = f'"{self.table}"."{self.column}"'
        return sql, []

然后使用此表达式构建查询

articles = Article.objects.all().annotate(
    labels=Subquery(
        Label.objects.all().annotate(
            article_label=RawCol(Article, 'label')
        ).filter(article_label__icontains=F('name')).values('name')[:1]
    )
).filter(labels__isnull=False)

这应该返回Article模型的实例,该实例的label字段包含Label模型的name字段中的值