Django ORM在一个查询中加入多对多关系

Dav*_*eza 4 django postgresql django-models django-orm

如果我们有2个具有多对多关系的模型A,B。

我想获得一个类似于此的sql查询:

SELECT *
FROM a LEFT JOIN ab_relation 
ON ab_relation.a_id = a.id
JOIN b ON ab_relation.b_id = b.id;
Run Code Online (Sandbox Code Playgroud)

所以在Django,当我尝试:

A.objects.prefetch_related('bees')
Run Code Online (Sandbox Code Playgroud)

我收到2个类似的查询:

SELECT * FROM a;
SELECT ab_relation.a_id AS prefetch_related_val_a_id, b.* 
FROM b JOIN ab_relation ON b.id = ab_relation.b_id
WHERE ab_relation.a_id IN (123, 456... list of all a.id);
Run Code Online (Sandbox Code Playgroud)

鉴于A和B的表比较大,我发现django的方式满足我的需求太慢了。

问题是:是否可以通过ORM获得左联接手动编写的查询?

编辑以回答一些澄清:

  • 是的LEFT OUTER JOIN,最好是将a放在查询集中,而不仅仅是与B有关系的SQL(更新的sql)。

  • 中等大意味着每个〜4k行,太慢意味着〜3秒(在第一次加载时,在Redis缓存之前。)请记住,页面上还有其他查询。

  • 实际上,是的,我们只需要B.one_field,但是尝试了Prefetch('bees', queryset=B.objects.values('one_field'))一个错误后说您不能values在预取中使用。

  • queryset将用作多选表单域的选项,在这里我们需要用B.field中的额外字符串表示与B有关系的A对象。

dan*_*era 5

对于直接答案,请跳至第6点)

让我们一步一步地讲。

1)N:M选择。您说您想要这样的查询:

SELECT *
FROM a JOIN ab_relation ON ab_relation.a_id = a.id
JOIN b ON ab_relation.b_id = b.id;
Run Code Online (Sandbox Code Playgroud)

但这不是真正的N:M查询,因为您仅获得与AB相关的对象,该查询应使用outer joins。至少喜欢:

SELECT *
FROM a left outer JOIN 
     ab_relation ON ab_relation.a_id = a.id left outer JOIN 
     b ON ab_relation.b_id = b.id;
Run Code Online (Sandbox Code Playgroud)

在其他情况下,您只会A获得具有相关的模型B

2)阅读大表您说“中等大表”。然后,确定要从数据库读取整个表吗?在Web环境中,读取大量数据并不常见,在这种情况下,您可以分页数据。可能不是网络应用?为什么需要阅读这张大桌子?我们需要上下文来回答您的问题。您确定需要两个表中的所有字段吗?

3)选择*。确定您需要两个表中的所有字段吗?如果您只读取某些值,则此查询可能会运行得更快。

A.objects.values( "some_a_field", "anoter_a_field", "Bs__some_b_field" )
Run Code Online (Sandbox Code Playgroud)

4)作为总结。ORM是一个功能强大的工具,两次读取操作都是“快速”的。我写了一些想法,但也许我们需要更多的上下文来回答您的问题。什么表示适中的大表,wheat表示慢,这是您在处理这些数据,每个表的每一行有多少个字段或字节,...。

Editedd由于OP编辑了问题。

5)使用正确的UI控件。你说:

queryset将用作多选表单域的选项,在这里我们需要用B.field中的额外字符串表示与B有关系的A对象。

看起来像反模式,将表单的4k行发送到客户端。我建议您转到仅加载所需数据的实时控件。例如,按某些文本进行过滤。看看django-select2很棒的项目。

6)你说

问题是:是否可以通过ORM获得左联接手动编写的查询?

答案是:是的,你可以用它做的values,因为我说的第3点样品:MaterialResultatAprenentatge一个N:M关系:

>>> print( Material
          .objects
          .values( "titol", "resultats_aprenentatge__codi" )
          .query )
Run Code Online (Sandbox Code Playgroud)

查询:

SELECT "material_material"."titol", 
       "ufs_resultataprenentatge"."codi" 
FROM   "material_material" 
       LEFT OUTER JOIN "material_material_resultats_aprenentatge" 
                    ON ( "material_material"."id" = 
"material_material_resultats_aprenentatge"."material_id" ) 
LEFT OUTER JOIN "ufs_resultataprenentatge" 
ON ( 
"material_material_resultats_aprenentatge"."resultataprenentatge_id" = 
"ufs_resultataprenentatge"."id" ) 
ORDER  BY "material_material"."data_edicio" DESC 
Run Code Online (Sandbox Code Playgroud)