2014-01-22 71 views
9

我有以下型號:LEFT JOIN Django的ORM

class Volunteer(models.Model): 
    first_name = models.CharField(max_length=50L) 
    last_name = models.CharField(max_length=50L)  
    email = models.CharField(max_length=50L) 
    gender = models.CharField(max_length=1, choices=GENDER_CHOICES) 


class Department(models.Model): 
    name = models.CharField(max_length=50L, unique=True) 
    overseer = models.ForeignKey(Volunteer, blank=True, null=True) 
    location = models.CharField(max_length=100L, null=True) 


class DepartmentVolunteer(models.Model): 
    volunteer = models.ForeignKey(Volunteer) 
    department = models.ForeignKey(Department) 
    assistant = models.BooleanField(default=False) 
    keyman = models.BooleanField(default=False) 
    captain = models.BooleanField(default=False) 
    location = models.CharField(max_length=100L, blank=True, null=True) 

我想查詢有沒有分配給他們的志願者各部門。我可以這樣做使用以下查詢:

SELECT 
    vsp_department.name 
FROM 
    vsp_department 
LEFT JOIN vsp_departmentvolunteer ON vsp_department.id = vsp_departmentvolunteer.department_id 
WHERE 
    vsp_departmentvolunteer.department_id IS NULL; 

是否有這樣做的更多的Django樣的方式或者我應該只是原始的SQL去?

回答

15

您可以通過查找中的向後關係來完成此操作。

>>> qs = Department.objects.filter(departmentvolunteer__isnull=True).values_list('name', flat=True) 
>>> print(qs.query) 
SELECT "app_department"."name" FROM "app_department" LEFT OUTER JOIN 
"app_departmentvolunteer" ON ("app_department"."id" = "app_departmentvolunteer"."department_id") 
WHERE "app_epartmentvolunteer"."id" IS NULL 

這裏是查詢 「跨越多值的關係」 的文檔:https://docs.djangoproject.com/en/stable/topics/db/queries/#spanning-multi-valued-relationships

+0

感謝加入!我會比較你的解決方案與我的。 – hanleyhansen

+0

您的查詢是否與DepartmentVolunteer中的id字段進行覈對?或者,Django會建立關係並檢查department_id字段? – hanleyhansen

+1

它檢查'DepartmentVolunteer'中的'department'列,這是'Departement'的FK,因此與其ID相匹配。 –

3

這似乎是工作:

Department.objects.filter(departmentvolunteer__department__isnull=True) 

詳情請參閱docs

7

對我來說,需要自定義連接模型,它有隱含的字段
它在django 1.9上工作給我。
但它的柺杖更顯得
如果有人有更好的解決方案,請分享給人們

from django.db.models.sql.datastructures import Join 
from django.db.models.fields.related import ForeignObject 
from django.db.models.options import Options 
from myapp.models import Ace 
from myapp.models import Subject 

jf = ForeignObject(
    to=Subject, 
    on_delete=lambda: x, 
    from_fields=[None], 
    to_fields=[None], 
    rel=None, 
    related_name=None 
) 

jf.opts = Options(Ace._meta) 
jf.opts.model = Ace 
jf.get_joining_columns = lambda: (("subj", "name"),) 

j=Join(
    Subject._meta.db_table, Ace._meta.db_table, 
    'T1', "LEFT JOIN", jf, True) 

q=Ace.objects.filter(version=296) 
q.query.join(j) 

print q.query 

結果:有附加條件使用

SELECT 
    `ace`.`id`, 
    `ace`.`version_id`, 
    `ace`.`obj`, 
    `ace`.`subj`, 
    `ace`.`ACE_Type`, 
    `ace`.`ACE_Inheritance`, 
    `ace`.`ACE_Rights` 
FROM `ace` 
LEFT OUTER JOIN `core_subject` 
ON (`ace`.`subj` = `core_subject`.`name`) 
WHERE `ace`.`version_id` = 296 

這裏例子並設置表的別名(但它看起來像柺杖)

def join_to(self, table1, table2, field1, field2, queryset, alias=''): 
    """ 
    table1 base 
    """ 
    # 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],())] 
      wh = where_class(children) 
      return wh 
     return None 

    dpj = ForeignObject(
     to=table2, 
     on_delete=lambda: None, 
     from_fields=[None], 
     to_fields=[None], 
     rel=None, 
     related_name=None 
    ) 
    dpj.opts = Options(table1._meta) 
    dpj.opts.model = table1 
    dpj.get_joining_columns = lambda: ((field1, field2),) 
    dpj.get_extra_restriction = extra_join_cond 

    dj = Join(
     table2._meta.db_table, table1._meta.db_table, 
     'T', "LEFT JOIN", dpj, True) 

    ac = queryset._clone() 
    ac.query.join(dj) 
    # hook for set alias 
    alias and setattr(dj, 'table_alias', alias) 
    return ac 

我使用它

# how it use: 
from django.db.models.expressions import Col 

q = Something.objects \ 
    .filter(type__in=["'S'", "'U'", "'G'"]) \ 
    .exclude(name__in=("'sys'", "'INFORMATION_SCHEMA'")) \ 
    .annotate(
     ... some annotation fields 
     class_= Col(Permissions._meta.db_table, 
        Permissions._meta.get_field('field_name'), 
        output_field=IntegerField()), 
     Grant=Col(
      'T10', 
      Principals._meta.get_field('name'), 
      output_field=CharField()), 
    ).values('Grant') 

    ac = self.join_to(Principals, ServerPrincipals, 'sid', 'sid', q) 
    # here invoke "extra_join_cond" of function "join_to" 
    ac = self.join_to(Permissions, Columns, 'major_id', 'object_id', ac) 
    # here use alias table 
    ac = self.join_to(Permissions, Principals, 'grantor_id', 'principal_id', ac, 'T10') # T10 is alias 

sql'll是

SELECT 
    T10.name AS Grant 
FROM sys.principals 
    LEFT OUTER JOIN sys.server_principals 
     ON (sys.principals.sid = sys.server_principals.sid) 
    LEFT OUTER JOIN sys.columns 
     ON (sys.permissions.major_id = sys.columns.object_id 
     AND (
      (sys.columns.column_id = sys.permissions.minor_id)) 
    ) 
LEFT OUTER JOIN sys.principals T10 
    ON (sys.permissions.grantor_id = T10.principal_id) 
+1

優秀!你讓我的一天先生。爲了增加一點補充,我已經用我的查詢創建了Join,所以我的解決方案是重寫它;)'queryset.query.alias_map ['my_outer_table']。join_field.get_extra_restriction = extra_join_cond' –

+0

哦,好吧,我會也使用它 – madjardi

+1

謝謝@madjardi。你的答案幫助我解決了這個問題:http://stackoverflow.com/a/42816689/2367394 –

0

容易通過創建或

def get_queryset(self): 
    qs = super(AceViewSet, self).get_queryset() 
    qs = qs.select_related('xxx') 
    # construct all tables and the join dependence 
    qs.query.__str__() 

    qs.query.alias_map['xx_subject'].join_cols = (('xxx_id', 'uid'), ('xxx_id', 'ad_subject_id')) 
    qs.query.alias_map['xx_subject'].as_sql = partial(self.as_sql, qs.query.alias_map['xx_subject']) 
    return qs 

@staticmethod 
def as_sql(self, compiler, connection): 
    sql, params = Join.as_sql(self, compiler, connection) 
    or_sql = sql.replace("AND", "OR") 
    return or_sql, params 
FROM "xx_ace" 
    LEFT OUTER JOIN "xx_subject" 
    ON ("xx_ace"."xxx_id" = "xx_subject"."uid" OR "xx_ace"."xxx_id" = "xx_subject"."ad_subject_id")