左加入 Django ORM
Posted
技术标签:
【中文标题】左加入 Django ORM【英文标题】:LEFT JOIN 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 吗?
【问题讨论】:
【参考方案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。您的回答帮助我解决了这个问题:***.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")
【讨论】:
以上是关于左加入 Django ORM的主要内容,如果未能解决你的问题,请参考以下文章