Django ORM和SQL内部联接

Django ORM和SQL内部联接,django,django-models,inner-join,django-queryset,django-orm,Django,Django Models,Inner Join,Django Queryset,Django Orm,我正在尝试获取相关列表对象上属于特定from_date和to_date范围内的所有Horse对象。例如 Horse.objects.filter(listings__to_date__lt=to_date.datetime, listings__from_date__gt=from_date.datetime) 现在,据我所知,这个数据库查询创建了一个内部联接,它使我能够根据相关的上市日期找到所有的horse对象 我的问题是这到底是如何工作的,这可能归结为对内部连接如何实际工作的主要理解不足。

我正在尝试获取相关列表对象上属于特定from_date和to_date范围内的所有Horse对象。例如

Horse.objects.filter(listings__to_date__lt=to_date.datetime,
listings__from_date__gt=from_date.datetime)
现在,据我所知,这个数据库查询创建了一个内部联接,它使我能够根据相关的上市日期找到所有的horse对象

我的问题是这到底是如何工作的,这可能归结为对内部连接如何实际工作的主要理解不足。这个查询是否需要首先“检查”每个对象,以确定它是否有相关的列表对象?我想这可能证明是非常低效的,因为您可能有500万个horse对象,但没有相关的列表对象,但您仍然必须首先检查每个对象

或者,我可以从我的列表开始,先做如下操作:

Listing.objects.filter(to_date__lt=to_date.datetime, 
from_date__gt=from_date.datetime)
然后:

for listing in listing_objs:
    if listing.horse:
        horses.append(horse)
但这似乎也是我取得成绩的一种相当奇怪的方式

如果有人能帮助我理解Django中的查询是如何工作的,以及哪种查询方式最有效,那将是一个很大的帮助

这是我当前的模型设置:

class Listing(models.Model):

    to_date = models.DateTimeField(null=True, blank=True)
    from_date = models.DateTimeField(null=True, blank=True)
    promoted_to_date = models.DateTimeField(null=True, blank=True)
    promoted_from_date = models.DateTimeField(null=True, blank=True)

    # Relationships
    horse = models.ForeignKey('Horse', related_name='listings', null=True, blank=True)

class Horse(models.Model):
    created_date = models.DateTimeField(null=True, blank=True, auto_now=True)
    type = models.CharField(max_length=200, null=True, blank=True)
    name = models.CharField(max_length=200, null=True, blank=True)
    age = models.IntegerField(null=True, blank=True)
    colour = models.CharField(max_length=200, null=True, blank=True)
    height = models.IntegerField(null=True, blank=True)

您编写查询的方式实际上取决于您大部分时间想要返回的信息。如果您对马感兴趣,请从
Horse
查询。如果您对列表感兴趣,那么您应该从
列表中查询。这通常是正确的做法,尤其是在使用简单外键时

关于Django,您的第一个查询可能更好。我使用了稍微简单一点的模型来说明差异。我创建了一个
active
字段,而不是使用日期时间

In [18]: qs = Horse.objects.filter(listings__active=True)

In [19]: print(qs.query)
SELECT 
"scratch_horse"."id", 
"scratch_horse"."name" 
FROM "scratch_horse" 
INNER JOIN "scratch_listing" 
ON ( "scratch_horse"."id" = "scratch_listing"."horse_id" ) 
WHERE "scratch_listing"."active" = True
上面查询中的内部联接将确保您只获得具有列表的马。(大多数)数据库非常擅长使用联接和索引来过滤不需要的行

如果
Listing
非常小,而
Horse
相当大,那么我希望数据库只查看列表表,然后使用索引获取马的正确部分,而不进行完整的表扫描(检查每匹马)。不过,您需要运行查询并检查数据库正在做什么。EXPLAIN(或您使用的任何数据库)非常有用。如果您正在猜测数据库正在做什么,那么您可能错了

请注意,如果您需要访问每匹
马的
列表
,那么每次访问
马的.listings
时,您都将执行另一个查询。如果您需要访问
列表
,可以通过执行单个查询并将其存储在缓存中来帮助您

现在,您的第二个查询:

In [20]: qs = Listing.objects.filter(active=True).select_related('horse')

In [21]: print(qs.query)
SELECT 
"scratch_listing"."id", 
"scratch_listing"."active", 
"scratch_listing"."horse_id", 
"scratch_horse"."id", 
"scratch_horse"."name" 
FROM "scratch_listing" 
LEFT OUTER JOIN "scratch_horse" 
ON ( "scratch_listing"."horse_id" = "scratch_horse"."id" ) 
WHERE "scratch_listing"."active" = True
这是一个左连接,这意味着右侧可以包含NULL。在本例中,右侧为
Horse
。如果您有很多没有马的列表,那么这将执行得非常糟糕,因为它将返回每个活动列表,无论是否有马与之关联。不过,您可以使用
.filter(active=True,horse\u isnull=False)
来解决这个问题

请注意,我使用了
select_related
,它连接了表,这样您就可以访问
listing.horse
,而无需再次查询

现在我可能应该问一下,为什么所有字段都可以为空。这通常是一个糟糕的设计选择,尤其是对外国人来说。你会有一个与马无关的列表吗?如果不是,则清除空值。你会有一匹没有名字的马吗?如果不是,则清除空值

所以答案是,大多数时候做看起来很自然的事情。如果您知道一个特定的表将会很大,那么您必须检查查询计划器(解释),查看添加/使用筛选/连接条件的索引,或者从关系的另一端进行查询