2014-04-07 212 views
3

我有一個表,其中包含一些網站的列表和一個統計他們的表。Django ORM。加入子查詢

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_date沒有記錄在stats表位。因爲在這種情況下,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?

謝謝。

回答

0

看它是這樣的:你想看到與某一特定日期伴隨網站的數據,轉化爲統計:

Stats.objects.filter(date=my_date).select_related('site') 
+0

謝謝,Suor。但在這種情況下,Django ORM使用'INNER JOIN'將統計信息連接到站點,然後應用按日期過濾。如果統計信息表不包含某個網站和日期的記錄,則該網站不會包含在結果中。我想,只有一種方法可以解決問題 - 加入過濾的統計表。 – psln

+0

我不太瞭解你。如果統計表不包含記錄,那麼您沒有日期並且不應返回行。如果你的意思是這個統計信息沒有網站,那麼在加入時就會被淘汰,而不是它在Django中的工作方式。你在這種情況下設置site = models.ForeignKey('Site',null = True),Django爲你添加相同的代碼。 – Suor

+0

網站可以在某個日期沒有統計記錄。我需要獲取所有網站和統計信息,這些信息存在於此日期。 F.E.如果'site'表包含記錄'site_1'和'site_2','stats'表只包含'stats_1(site = site_1,date = my_date)',我想獲得'[{site_1,stats_1},{site_2,NULL }]'。但是你的代碼只會返回'[{site_1,stats_1}]'。 – psln

3

我有一個類似的問題,寫了下面的效用函數,用於將左外連接在使用Django ORM的子查詢集上。

util是從給出的解決方案中派生出來的,該解決方案使用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') 

而下面的語句將打印類似於您的示例查詢(與子查詢)查詢:爲了您的查詢,如下所示,你可以使用它。

q.query.__str__() 
0

在Django中使用2.0 FilteredRelation

Site.objects.annotate(
    t=FilteredRelation(
     'stats', condition=Q(stats__date='my-date') 
).filter(t__google_pr__in=[...])