9

我有一个包含一些网站列表的表格和一个包含它们统计信息的表格。

class Site(models.Model):
    domain_name = models.CharField(
        max_length=256,
        unique=True,
    )


class Stats(models.Model):
    date = models.DateField()
    site = models.ForeignKey('Site')
    google_pr = models.PositiveIntegerField()

    class Meta:
        unique_together = ('site', 'date')

我想查看具体日期的所有网站和统计数据。如果该日期的统计记录不存在,则选择必须仅包含站点。

如果我使用:

Site.objects.filter(stats__date=my_date)

我不会得到my_datestats表中没有记录的网站。因为在这种情况下,SQL 查询将如下所示:

SELECT *
FROM site
LEFT OUTER JOIN stats ON site.id = stats.site_id
WHERE stats.date = 'my_date'

查询条件将排除具有 NULL 日期的记录,并且没有统计信息的站点将不包括在选择中。

在我的情况下,我需要加入统计表,该表已经按日期过滤:

SELECT *
FROM site
LEFT OUTER JOIN
  (SELECT *
   FROM stats
   WHERE stats.date = 'my-date') AS stats
ON site.id = stats.site_id

如何将此查询转换为 Django ORM?

谢谢。

4

3 回答 3

10

在 Django v2.0 中使用FilteredRelation

Site.objects.annotate(
    t=FilteredRelation(
        'stats', condition=Q(stats__date='my-date')
).filter(t__google_pr__in=[...])
于 2018-01-10T05:35:32.197 回答
6

我遇到了类似的问题,并编写了以下实用函数,用于使用 Django ORM 在子查询集上添加左外连接。

该实用程序源自使用 Django ORM 将自定义左外连接添加到另一个表(不是子查询)的解决方案。这是该解决方案:https ://stackoverflow.com/a/37688104/2367394

以下是 util 和所有相关代码:

from django.db.models.fields.related import ForeignObject
from django.db.models.options import Options
from django.db.models.sql.where import ExtraWhere
from django.db.models.sql.datastructures import Join


class CustomJoin(Join):
    def __init__(self, subquery, subquery_params, parent_alias, table_alias, join_type, join_field, nullable):
        self.subquery_params = subquery_params
        super(CustomJoin, self).__init__(subquery, parent_alias, table_alias, join_type, join_field, nullable)

    def as_sql(self, compiler, connection):
        """
        Generates the full
        LEFT OUTER JOIN (somequery) alias ON alias.somecol = othertable.othercol, params
        clause for this join.
        """
        params = []
        sql = []
        alias_str = '' if self.table_alias == self.table_name else (' %s' % self.table_alias)
        params.extend(self.subquery_params)
        qn = compiler.quote_name_unless_alias
        qn2 = connection.ops.quote_name
        sql.append('%s (%s)%s ON (' % (self.join_type, self.table_name, alias_str))
        for index, (lhs_col, rhs_col) in enumerate(self.join_cols):
            if index != 0:
                sql.append(' AND ')
            sql.append('%s.%s = %s.%s' % (
                qn(self.parent_alias),
                qn2(lhs_col),
                qn(self.table_alias),
                qn2(rhs_col),
            ))
        extra_cond = self.join_field.get_extra_restriction(
            compiler.query.where_class, self.table_alias, self.parent_alias)
        if extra_cond:
            extra_sql, extra_params = compiler.compile(extra_cond)
            extra_sql = 'AND (%s)' % extra_sql
            params.extend(extra_params)
            sql.append('%s' % extra_sql)
        sql.append(')')
        return ' '.join(sql), params

def join_to(table, subquery, table_field, subquery_field, queryset, alias):
    """
    Add a join on `subquery` to `queryset` (having table `table`).
    """
    # here you can set complex clause for join
    def extra_join_cond(where_class, alias, related_alias):
        if (alias, related_alias) == ('[sys].[columns]',
                                    '[sys].[database_permissions]'):
            where = '[sys].[columns].[column_id] = ' \
                    '[sys].[database_permissions].[minor_id]'
            children = [ExtraWhere([where], ())]
            return where_class(children)
        return None
    foreign_object = ForeignObject(to=subquery, from_fields=[None], to_fields=[None], rel=None)
    foreign_object.opts = Options(table._meta)
    foreign_object.opts.model = table
    foreign_object.get_joining_columns = lambda: ((table_field, subquery_field),)
    foreign_object.get_extra_restriction = extra_join_cond
    subquery_sql, subquery_params = subquery.query.sql_with_params()
    join = CustomJoin(
        subquery_sql, subquery_params, table._meta.db_table,
        alias, "LEFT JOIN", foreign_object, True)

    queryset.query.join(join)

    # hook for set alias
    join.table_alias = alias
    queryset.query.external_aliases.add(alias)

    return queryset

join_to是您要使用的效用函数。对于您的查询,您可以按如下方式使用它:

sq = Stats.objects.filter(date=my_date)
q = Site.objects.filter()
q = join_to(Site, sq, 'id', 'site_id', q, 'stats')

以下语句将打印一个类似于您的示例查询(带有子查询)的查询。

print q.query
于 2017-03-15T17:26:47.493 回答
-1

以这种方式看待它:您希望查看特定日期的附带站点数据的统计信息,即:

Stats.objects.filter(date=my_date).select_related('site')
于 2014-04-07T01:52:46.097 回答