内部连接子查询 Django ORM 等价物
Inner join subquery Django ORM equivalent
我有三个相关的 tables:
- 模块
+-------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+-------+
| module_id | int(11) | NO | PRI | NULL | |
+-------------+-------------+------+-----+---------+-------+
- 事件
+------------------+--------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------------------+------+-----+---------+----------------+
| event_id | int(11) | NO | PRI | NULL | auto_increment |
| event_time | datetime(4) | NO | | NULL | |
| module_id | int(11) | NO | MUL | NULL | |
| file_id | int(11) | YES | MUL | NULL | |
+------------------+--------------------------+------+-----+---------+----------------+
- 文件
+--------------+-----------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-----------------------------+------+-----+---------+----------------+
| file_id | int(11) | NO | PRI | NULL | auto_increment |
| path | varchar(512) | NO | UNI | NULL | |
+--------------+-----------------------------+------+-----+---------+----------------+
所以,有模块、事件和文件。 (未使用的字段从 table 中删除以简化)。
目标:
我想获取每个模块上发生的最新事件及其文件路径。
我试过的:
因此,为此,起初我使用子查询在 Django 上创建了一个简单的实现:
last_event_subquery = Event.objects.filter(
module_id__module_id=OuterRef('module__id')
).order_by('-event_time', '-event_id')
modules = Module.objects.all().annotate(
last_event_path=Subquery(last_event_subquery.values('file_id__path')[:1])
).annotate(
last_event_id=Subquery(last_event_subquery.values('event_id')[:1])
).annotate(
last_event_datetime=Subquery(last_event_subquery.values('event_time')[:1])
)
但是,我发现 运行在事件 table 中处理超过 100 万条记录 的速度非常慢。当然,那里有几个索引可以优化所有的东西,但即使这样我也找不到不到 5 秒到 运行 的索引组合,这太多了 我。然后,我看到了原因,等价的SQL查询太傻了:
SELECT `module`.`module_id`,
(SELECT U2.`path` FROM `events` U0 LEFT OUTER JOIN `files` U2 ON (U0.`file_id` = U2.`file_id`)
WHERE U0.`module_id` = (`modules`.`module_id`) ORDER BY U0.`event_time` DESC, U0.`event_id` DESC LIMIT 1)
AS `last_event_path`,
(SELECT U0.`event_id` FROM `events` U0
WHERE U0.`module_id` = (`modules`.`module_id`) ORDER BY U0.`event_time` DESC, U0.`event_id` DESC LIMIT 1)
AS `last_event_id`,
(SELECT U0.`event_time` FROM `events` U0
WHERE U0.`module_id` = (`modules`.`module_id`) ORDER BY U0.`event_time` DESC, U0.`event_id` DESC LIMIT 1)
AS `last_event_time` FROM `events`
如您所见,它重复了三次子查询。
因此,我决定尽我所能在 SQL 中尝试一下,并且努力完成以下工作:
SELECT module.module_id,
events.event_id,
events.event_time,
files.path
FROM modules INNER JOIN events ON events.event_id =
(SELECT events.event_id FROM events
WHERE modules.module_id = events.module_id
ORDER BY events.event_time DESC, events.event_id DESC LIMIT 1)
INNER JOIN files ON files.file_id = events.file_id;
这 运行 秒在 0.001 秒。所以,现在的问题是我无法用 Django ORM 语言完成这项工作。当然,我可以只放置原始 SQL 查询就可以完成,但是我将如何忍受这样的耻辱?
我调查了整个 Django 文档,在所有 Whosebug 问题上苦苦挣扎,但我找不到答案。我得到的最近的是 ,但问题是我无法将每个模块限制为一个结果。
我也尝试过 FilteredRelation,但无法获得合适的过滤器。
我也不能使用 select_related(),因为它是与外键的反向关系。
我不能将 distinct() 与列字段一起使用,因为我使用的是 MySQL(更具体地说,MariaDB 版本 10.3)。
您对如何解决这个问题有什么建议吗?
谢谢!
所以,我自己找到了答案:)
它生成与我想要的完全相同的 SQL,除了子句的顺序,它根本不影响查询。关键是将 .filter() 与关系一起使用。在 filter() 中使用 子查询(我不知道这是可能的)就成功了。我的灵感来自 this answer。
last_event_subquery = Event.objects.filter(
module_id__module_id=OuterRef('module_id')
).order_by('-event_time', '-event_id')
modules = Module.objects.filter(
event__event_id=Subquery(
last_event_subquery.values('module_id')[:1])
).values('id', 'event__event_id', 'event__event_time', 'event__file__path')
这会产生以下内容 SQL:
SELECT `modules`.`module_id`,
`events`.`event_id`,
`events`.`event_time`,
`files`.`path`
FROM `modules`
INNER JOIN `events` ON (`modules`.`module_id` = `events`.`module_id`)
LEFT OUTER JOIN `files` ON (`events`.`file_id` = `files`.`file_id`)
WHERE `events`.`event_id` =
(SELECT U0.`event_id` FROM `events` U0
WHERE U0.`module_id` = (`modules`.`module_id`)
ORDER BY U0.`event_time` DESC, U0.`event_id` DESC LIMIT 1)
我希望这对其他人有用。
我有三个相关的 tables:
- 模块
+-------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+-------+
| module_id | int(11) | NO | PRI | NULL | |
+-------------+-------------+------+-----+---------+-------+
- 事件
+------------------+--------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------------------+------+-----+---------+----------------+
| event_id | int(11) | NO | PRI | NULL | auto_increment |
| event_time | datetime(4) | NO | | NULL | |
| module_id | int(11) | NO | MUL | NULL | |
| file_id | int(11) | YES | MUL | NULL | |
+------------------+--------------------------+------+-----+---------+----------------+
- 文件
+--------------+-----------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-----------------------------+------+-----+---------+----------------+
| file_id | int(11) | NO | PRI | NULL | auto_increment |
| path | varchar(512) | NO | UNI | NULL | |
+--------------+-----------------------------+------+-----+---------+----------------+
所以,有模块、事件和文件。 (未使用的字段从 table 中删除以简化)。
目标: 我想获取每个模块上发生的最新事件及其文件路径。
我试过的: 因此,为此,起初我使用子查询在 Django 上创建了一个简单的实现:
last_event_subquery = Event.objects.filter(
module_id__module_id=OuterRef('module__id')
).order_by('-event_time', '-event_id')
modules = Module.objects.all().annotate(
last_event_path=Subquery(last_event_subquery.values('file_id__path')[:1])
).annotate(
last_event_id=Subquery(last_event_subquery.values('event_id')[:1])
).annotate(
last_event_datetime=Subquery(last_event_subquery.values('event_time')[:1])
)
但是,我发现 运行在事件 table 中处理超过 100 万条记录 的速度非常慢。当然,那里有几个索引可以优化所有的东西,但即使这样我也找不到不到 5 秒到 运行 的索引组合,这太多了 我。然后,我看到了原因,等价的SQL查询太傻了:
SELECT `module`.`module_id`,
(SELECT U2.`path` FROM `events` U0 LEFT OUTER JOIN `files` U2 ON (U0.`file_id` = U2.`file_id`)
WHERE U0.`module_id` = (`modules`.`module_id`) ORDER BY U0.`event_time` DESC, U0.`event_id` DESC LIMIT 1)
AS `last_event_path`,
(SELECT U0.`event_id` FROM `events` U0
WHERE U0.`module_id` = (`modules`.`module_id`) ORDER BY U0.`event_time` DESC, U0.`event_id` DESC LIMIT 1)
AS `last_event_id`,
(SELECT U0.`event_time` FROM `events` U0
WHERE U0.`module_id` = (`modules`.`module_id`) ORDER BY U0.`event_time` DESC, U0.`event_id` DESC LIMIT 1)
AS `last_event_time` FROM `events`
如您所见,它重复了三次子查询。
因此,我决定尽我所能在 SQL 中尝试一下,并且努力完成以下工作:
SELECT module.module_id,
events.event_id,
events.event_time,
files.path
FROM modules INNER JOIN events ON events.event_id =
(SELECT events.event_id FROM events
WHERE modules.module_id = events.module_id
ORDER BY events.event_time DESC, events.event_id DESC LIMIT 1)
INNER JOIN files ON files.file_id = events.file_id;
这 运行 秒在 0.001 秒。所以,现在的问题是我无法用 Django ORM 语言完成这项工作。当然,我可以只放置原始 SQL 查询就可以完成,但是我将如何忍受这样的耻辱?
我调查了整个 Django 文档,在所有 Whosebug 问题上苦苦挣扎,但我找不到答案。我得到的最近的是
我也尝试过 FilteredRelation,但无法获得合适的过滤器。 我也不能使用 select_related(),因为它是与外键的反向关系。 我不能将 distinct() 与列字段一起使用,因为我使用的是 MySQL(更具体地说,MariaDB 版本 10.3)。
您对如何解决这个问题有什么建议吗?
谢谢!
所以,我自己找到了答案:)
它生成与我想要的完全相同的 SQL,除了子句的顺序,它根本不影响查询。关键是将 .filter() 与关系一起使用。在 filter() 中使用 子查询(我不知道这是可能的)就成功了。我的灵感来自 this answer。
last_event_subquery = Event.objects.filter(
module_id__module_id=OuterRef('module_id')
).order_by('-event_time', '-event_id')
modules = Module.objects.filter(
event__event_id=Subquery(
last_event_subquery.values('module_id')[:1])
).values('id', 'event__event_id', 'event__event_time', 'event__file__path')
这会产生以下内容 SQL:
SELECT `modules`.`module_id`,
`events`.`event_id`,
`events`.`event_time`,
`files`.`path`
FROM `modules`
INNER JOIN `events` ON (`modules`.`module_id` = `events`.`module_id`)
LEFT OUTER JOIN `files` ON (`events`.`file_id` = `files`.`file_id`)
WHERE `events`.`event_id` =
(SELECT U0.`event_id` FROM `events` U0
WHERE U0.`module_id` = (`modules`.`module_id`)
ORDER BY U0.`event_time` DESC, U0.`event_id` DESC LIMIT 1)
我希望这对其他人有用。