【问题标题】:LEFT JOIN Django ORM左加入 Django ORM
【发布时间】:2014-02-11 20:32:46
【问题描述】:

我有以下型号:

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 
    d.name 
FROM   
    vsp_department AS d
LEFT JOIN vsp_departmentvolunteer AS dv
ON d.id = dv.department_id  
WHERE
    dv.department_id IS NULL;

是否有更类似于 django 的方式来执行此操作,或者我应该只使用原始 sql 吗?

【问题讨论】:

    标签: python sql django django-models django-orm


    【解决方案1】:

    您可以通过遵循查找中的向后关系来做到这一点。

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

    【讨论】:

    • 谢谢!我会将您的解决方案与我的解决方案进行比较。
    • 您的查询是否检查了 DepartmentVolunteer 中的 id 字段?还是 Django 会建立关系并检查 department_id 字段?
    • 它正在检查 DepartmentVolunteer 中的 department 列,该列是 Departement 的 FK,因此匹配其 ID。
    • 这是一个外连接。如果您不希望那里也有正确的连接匹配怎么办...?
    • 我的查询是INNER JOIN。我正在使用django 1.11.20。关于如何进行左连接的任何想法?我需要一个INNER JOIN 和一些LEFT JOIN。我一直在尝试搜索它,但无法找到解决方案。
    【解决方案2】:

    对我来说,需要具有隐式字段的自定义连接模型
    它在 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 会是

    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)
    

    【讨论】:

    • 太棒了!先生,你让我很开心。为了添加一点补充,我已经用我的查询创建了 Join,所以我的解决方案是覆盖它;)queryset.query.alias_map['my_outer_table'].join_field.get_extra_restriction = extra_join_cond
    • 谢谢@madjardi。您的回答帮助我解决了这个问题:stackoverflow.com/a/42816689/2367394
    【解决方案3】:

    这似乎有效:

    Department.objects.filter(departmentvolunteer__department__isnull=True)
    

    更多详情请见docs

    【讨论】:

      【解决方案4】:

      通过OR

      创建自定义连接
      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")
      

      【讨论】:

        猜你喜欢
        • 2021-07-03
        • 2014-08-30
        • 1970-01-01
        • 1970-01-01
        • 2010-10-20
        • 2020-03-11
        • 2019-08-12
        • 1970-01-01
        • 2020-05-18
        相关资源
        最近更新 更多