У меня есть приложение Django, которое подключено к базе данных MySQL. База данных полна записей - несколько миллионов из них.
Мои модели выглядят так:
class LAN(models.Model):
...
class Record(models.Model):
start_time = models.DateTimeField(...)
end_time = models.DateTimeField(...)
ip_address = models.CharField(...)
LAN = models.ForeignKey(LAN, related_name="records", ...)
bytes_downloaded = models.BigIntegerField(...)
bytes_uploaded = models.BigIntegerField(...)
Каждая запись отражает окно времени и показывает, является ли конкретный IP-адрес конкретным Локальная сеть выполняла любую загрузку или загрузку в течение этого окна.
Что мне нужно знать, это то, что: учитывая дату начала и дату окончания, дайте мне таблицу, в которой ДНИ ДАННОЙ ЛВС имели ЛЮБОЕ действие (имеет какие-либо записи )
Пример:
В период с 1 января по 31 января скажите, в какие ДНИ ЛАНА на них были ЛЮБЫЕ записи
Предположим, что один раз в в то время как локальная сеть будет отключаться на несколько дней и в эти дни не будет никаких записей или каких-либо действий.
Мое решение:
Я могу сделать это медленным путем, добавив некоторые методы к моя модель локальной сети:
class LAN(models.Model):
...
# Returns True if there are records for the current LAN between 2 given dates
# Returns False otherwise
def online(self, start, end):
criterion1 = Q(start_time__lt=end)
criterion2 = Q(end_time__gt=start)
return self.records.filter(criterion1 & criterion2).exists()
# Returns a list of days that a LAN was online for between 2 given dates
def list_online_days(self, start, end):
start_date = timezone.make_aware(timezone.datetime.strptime(start, "%b %d, %Y"))
end_date = timezone.make_aware(timezone.datetime.strptime(end, "%b %d, %Y"))
end_date = end_date.replace(hour=23, minute=59, second=59, microsecond=999999)
days_online = []
current_date = start.astimezone()
while current_date <= end:
start_of_day = current_date.replace(hour=0, minute=0, second=0, microsecond=0)
end_of_day = current_date.replace(hour=23, minute=59, second=59, microsecond=999999)
if self.online(start=start_of_day, end=end_of_day):
days_online.append(current_date.date())
current_date += timezone.timedelta(days=1)
return days_online
В этот момент я могу выполнить:
lan = LAN.objects.get(id=1) # Or whatever LAN I'm interested in
days_online = lan.list_online_days(start="Jan 1, 2020", end="Jan 31, 2020")
Это работает, но в результате выполняется один запрос в день между моей датой начала и датой окончания. В этом случае 31 запрос (1 января, 2 января и др. c.). Это делает его очень, очень медленным для больших периодов времени, так как ему нужно go просмотреть все записи в базе данных 31 раз. Индексация базы данных помогает, но она все еще медленная с достаточным количеством данных в базе данных.
Есть ли способ сделать один запрос к базе данных, чтобы получить то, что мне нужно?
Мне кажется, что это будет выглядеть что-то вроде этого, но я не совсем понимаю это правильно:
lan.records.filter(criterion1 & criterion2).annotate(date=TruncDay('start_time')).order_by('date').distinct().values('date').annotate(exists=Exists(SOMETHING))
Первая часть:
lan.records.filter(criterion1 & criterion2).annotate(date=TruncDay('start_time')).order_by('date').distinct().values('date')
Кажется, дает мне то, что я хочу - одно значение в день, но Я не уверен, как аннотировать результат с помощью поля с существующим значением, которое показывает, существуют ли какие-либо записи в этот день.
Примечание. Это упрощенная версия моего приложения, а не точные модели и поля, поэтому если некоторые вещи могут быть улучшены, например, не использовать CharField для поля ip_address, не слишком фокусироваться на этом