LEFT JOIN Django ORM LEFT JOIN Django ORM python python

LEFT JOIN Django ORM


You can do this by following the backwards relation in the lookup.

>>> 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

Here are the docs on queries "Spanning multi-valued relationships": https://docs.djangoproject.com/en/stable/topics/db/queries/#spanning-multi-valued-relationships


To me were need custom join models, that have implicit fields
it work to me on django 1.9.
but it more seem on the crutch
If someone have more elegant solution please share for people

from django.db.models.sql.datastructures import Joinfrom django.db.models.fields.related import ForeignObjectfrom django.db.models.options import Optionsfrom myapp.models import Acefrom myapp.models import Subjectjf = 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 = Acejf.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

result:

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

here example of use with additional condition and set table alias(but it seem as crutch)

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

i use it by

# 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 be

SELECT    T10.name    AS GrantFROM 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)


This seems to be working:

Department.objects.filter(departmentvolunteer__department__isnull=True)

See docs for more details.