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

Django ORM join many to many relation in one query

如果我们有 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;

所以在 django 中,当我尝试时:

A.objects.prefetch_related('bees')

我收到 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);

考虑到 A 和 B 有中等大的表,我发现 django 的方式对我的需要来说太慢了。

问题是:是否可以通过ORM获取left join手写查询?

编辑以回答一些说明:

直接回答请跳至第 6 点)

我们一步步说吧

1) N:M select。你说你想要这样的查询:

SELECT *
FROM a JOIN ab_relation ON ab_relation.a_id = a.id
JOIN b ON ab_relation.b_id = b.id;

但这不是真正的 N:M 查询,因为您只获取 A-B 相关对象查询应该使用 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;

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

2)读大tables你说"moderately big tables"。那么,您确定要从数据库中读取整个 table 吗?这在读取大量数据的 Web 环境中并不常见,在这种情况下,您可以对数据进行分页。可能不是网络应用程序?为什么你需要阅读这么大的 tables?我们需要上下文来回答您的问题。您确定需要两个 table 的所有字段吗?

3) Select * 来自 您确定需要来自两个 table 的所有字段吗?如果您只读取某些值,则此查询可能 运行 更快。

A.objects.values( "some_a_field", "anoter_a_field", "Bs__some_b_field" )

4) 总结。 ORM是一个强大的工具,两个单读操作是"fast"。我写了一些想法,但也许我们需要更多的背景来回答你的问题。什么意思是中等大 tables,小麦意味着慢,你用这些数据做什么,每个 table 的每行有多少字段或字节,......

Editedd 因为 OP 已经编辑了问题。

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

The queryset will be used as options for a multi-select form-field, where we will need to represent A objects that have a relation with B with an extra string from the B.field.

向客户端发送表单的 4k 行似乎是一种反模式。我建议您转移到只加载所需数据的实时控件。例如,按某些文本进行过滤。看看 django-select2 很棒的项目。

6) 你说

The question is: Is it possible to obtain the left join manually written query through the ORM?

答案是:,你可以使用values来完成,正如我在第3点所说的。示例:MaterialResultatAprenentatge 是一个 N:M 关系:

>>> print( Material
          .objects
          .values( "titol", "resultats_aprenentatge__codi" )
          .query )

查询:

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