У меня есть три связанных таблицы:
+-------------+-------------+------+-----+---------+-------+
| 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 | |
+--------------+-----------------------------+------+-----+---------+----------------+
Итак, есть Модули , События и Файлы . (Неиспользуемые поля обрезаны из таблиц для упрощения).
Цель: Я хочу получить последнее событие, которое произошло на каждом модуле, и его путь к файлу .
Что я пробовал: Итак, для этого я сначала создал наивную реализацию на 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])
)
Но я обнаружил, что запуск этого более 1 миллион записей в таблице событий явно медленно. Конечно, есть несколько индексов для оптимизации всего этого, но даже при этом я не смог найти комбинацию индексов, для запуска которой требуется менее 5 секунд, что слишком много imo . Затем, я увидел причину, эквивалентный запрос 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 документам, боролся со всеми вопросами переполнения стека , но я не мог найти ответ. Самое близкое, что я получил, было это , но проблема в том, что я не могу получить этот ограниченный один результат на модуль.
Я также попробовал FilteredRelation, но не смог получить соответствующий фильтр. Я также не могу использовать select_related (), потому что это обратное отношение с ForeignKey. Я также не могу использовать Different () с полем столбца, потому что я использую MySQL (точнее, MariaDB версии 10.3).
У вас есть какой-нибудь совет, как подойти к этому?
Спасибо!