Django ORM 中的高级过滤。需要一个查询解决方案而不是递归

Advanced filtering in Django ORM. Need a one query solution instead of recursion

我对 Django ORM 中的高级过滤有疑问。

型号:

class ClubAgentMember(models.Model):
    club = models.ForeignKey(Club, on_delete=models.CASCADE, related_name='club_agent_players')
    agent = models.ForeignKey(User, on_delete=models.CASCADE, related_name='agent_club_players')
    member = models.ForeignKey(User, on_delete=models.CASCADE, related_name='member_club_agents')

    created_at = models.DateTimeField(auto_now_add=True)

目标是这样的: 例如,我有初始 agent_id = 15,我需要找到连接到初始代理的代理的所有代理 ID。我知道如何通过递归来做到这一点。在小样本上很好,但在更大的样本上它会大大减慢 DB。所以我需要在 1 个查询中提取所有数据。 结果查询集应该是 [ 15, 19, 22] – agent_id

如何阅读图表: 初始代理有 id= 15(黄色)。 id [18, 19, 27, 28] 的成员附加到此代理(橙色)。其中一名成员(此示例中只有一名,可能是 2 名或更多名或全部,取决于 )本身就是代理人,编号 19(绿色)。 在下一级,我们有初始代理 19(绿色),他有成员 [22, 31, 32] 附属于他。其中一个是特工本人 22(红色)。 下一级特工ID=22,他的成员是[37, 38, 39]。 None 其中有代理人。所以我们到这里就完成了。

最后,我需要获得此序列中所有已连接代理(已连接到初始代理)的 ID,即我们场景中的 agent_id [15, 19, 22]。 初始代理 ID 来自 request.user.id,可能会有所不同

Raw SQL 也是可能的,如果不能通过 Django ORM 做的话 Django版本我有2.0.7

提前致谢。

一种选择是将所有数据查询到内存中并在 Python 中进行递归。为了减少处理时间,可以使用 .values_list() 将查询减少到所需的最少数据(没有 ORM 转换为 类,只有原始值)并且使用 Python sets 可以允许快速查找。

代码可能与此类似:

import collections
agents = collections.defaultdict(set)
for agent_id, member_id in ClubAgentMember.objects.values_list('agent_id', 'member_id'):
    agents[agent_id].add(member_id)

start_agent_id = 15
agents_to_check = {start_agent_id, }
result_agent_set = set()

while len(agents_to_check) > 0:
    agent_id = agents_to_check.pop()
    result_agent_set.add(agent_id)

    for member_id in agents.get(agent_id, []):
        if member_id in agents:
            agents_to_check.add(member_id)

print('result_agent_set', result_agent_set)

你在评论中说

On a sample with 30.000 enties for example, it would be over one second (overal API time), which is not good.

对于您的用例和您的数据,此代码提案是否仍然变慢?

create materialized view  players_view as
(
WITH RECURSIVE players AS (
    SELECT agent_id,
           player_id
    FROM clubs_clubagentplayer
    WHERE agent_id = 15
    UNION
    SELECT sub.agent_id,
           sub.player_id
    FROM clubs_clubagentplayer as sub
             INNER JOIN players as main ON main.player_id = sub.agent_id
)
SELECT DISTINCT agent_id
FROM players
    )