Django ORM 和 SQL 内连接

3

我正在尝试获取与相关清单对象的特定从日期和到日期范围内的所有马对象。例如:

Horse.objects.filter(listings__to_date__lt=to_date.datetime,
listings__from_date__gt=from_date.datetime)

现在我理解这个数据库查询创建了一个内部连接,从而使我能够基于相关的上市日期找到所有我的马对象。
我的问题是这个查询如何运作,这可能归结于对内部连接实际上如何工作的缺乏理解。这个查询需要先“检查”每一个马对象,以确定它是否有相关的上市对象吗?我想这可能会证明效率不高,因为你可能有500万个马对象没有相关的上市对象,但你仍然需要先检查每一个对象?
或者我可以从我的列表开始,首先做这样的事情:
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)
1个回答

1

通常情况下,你编写查询的方式取决于你想要获取什么信息。如果你对马感兴趣,则应从Horse查询。如果你对列表感兴趣,则应从Listing查询。这通常是正确的做法,特别是在使用简单外键时。

就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相对较大,则我希望数据库只会查看Listing表,然后使用索引获取Horse的正确部分,而不是进行全表扫描(检查每匹马)。但是您需要运行查询并检查您的数据库正在执行什么。EXPLAIN(或您使用的任何数据库)非常有用。如果您猜测数据库正在做什么,您可能是错误的。
请注意,如果您需要访问每匹马的listings,则每次访问horse.listings时都将执行另一个查询。如果您需要访问listings,则prefetch_related可以帮助您通过执行单个查询并将其存储在缓存中。
现在,您的第二个查询:
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

这是一个LEFT连接,这意味着右侧可以包含NULL。在这种情况下,右侧是“Horse”。如果有很多没有马的列表,这将表现得非常糟糕,因为它会将每个活动列表都带回来,无论是否与马相关联。但你可以使用“.filter(active=True,horse__isnull=False)”来解决这个问题。
注意,我使用了“select_related”,它连接了表格,以便您能够访问“listing.horse”而不产生其他查询。
现在我应该问为什么所有字段都是可空的。这通常是一个可怕的设计选择,特别是对于ForeignKeys。你会有一个没有关联到马的列表吗?如果没有,请去掉空值。你会有一个没有名字的马吗?如果没有,请去掉空值。
所以答案是,大多数情况下要做自然的事情。如果您知道特定的表格将会很大,则必须检查查询规划器(EXPLAIN),查看在过滤/连接条件上添加/使用索引,或从关系的另一侧进行查询。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接