Как добавить префетч (или что-то подобное) в кверисет django, используя содержимое JSONField?

Фон

У меня есть сложное аналитическое приложение, использующее обычную реляционную структуру, где различные сущности обновляются с помощью модели CRUD.

Однако наша система является достаточно событийной, и управление историей и миграция данных с учетом этой истории превращается в настоящее мучение.

Итак, мы переходим к квази-неSQL подходу, в котором я использую JSON для представления данных со связанным списком патчей, представляющих историю.

Что касается структуры данных, то это очень элегантное решение и будет хорошо работать с другими сервисами. Что касается django, то сейчас я испытываю трудности с использованием ORM.

Моей базой данных является PostgreSQL.

Ситуация

Раньше у меня было что-то вроде (псевдокод):

class Environment(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

class Analysis(models.Model):
    created = models.DateTime(auto_now_add=True)
    environment = models.ForeignKey(
        "myapp.Environment",
        blank=True,
        null=True,
        related_name="analyses",
    )
    everything_else = models.JSONField()

Тогда как теперь у меня будет:

class Environment(models.Model):  
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)  
  
class Analysis(models.Model):  
    created = models.DateTime(auto_now_add=True)
    everything = models.JSONField()

Где everything может быть JSON-объект, выглядящий как...

{
  "environment": "2bf94e55-7b47-41ad-b81a-6cce59762160",
  "other_stuff": "lots of stuff"
}

Теперь я все еще могу полностью получить Environment для данного Analysis, потому что у меня есть его ID, но если я сделаю что-то вроде этого (снова псевдокод):

for analysis in Analysis.objects.filter(created=today).all():
   print(Environment.objects.get(id=analysis.everything['environment'])

Я, конечно, получаю N+1 ошибку, потому что я запрашиваю базу данных для среды на каждой итерации.

В простом скрипте я мог бы поместить все идентификаторы в список, а затем сделать один запрос для них всех... но это не очень хорошо работает со всеми встроенными модулями djangos, такими как методы сериализатора API и методы администратора. Я действительно хочу иметь возможность изменять набор запросов, чтобы получить то, что я хочу.

Вопрос

Учитывая такой набор запросов, как Analysis.objects.filter(created=today).all(), как я могу настроить этот набор, чтобы предварительно получить связанные объекты окружения, теперь, когда их идентификаторы находятся в поле JSONField?

Никогда не используйте JSON-поля, если это не оправдано.

JSON-поля - это костыль, на который люди опираются, потому что не могут потрудиться разработать правильное моделирование.

  1. Денормализуйте ваши json-данные в отдельные таблицы.
  2. Используйте историю django/другие встроенные/третьи сторонние инструменты для исторического отслеживания.

Json-поля запутают вашу логику и сделают все нерабочим. Уже второй раз я работаю над проектом, в котором инженеры думали, что это элегантно или просто, а в итоге потратили полгода или больше времени, чтобы исправить спагетти, вызванное ими.

Что касается предварительной выборки, вы можете использовать аннотации и подзапросы, однако все, что вы попытаетесь сделать с json-полями , будет дорогостоящим. В частности, потому что вы больше не сможете использовать преимущества операций базы данных, таких как объединения/индексированный поиск и т. д...

Итак, как бы я поступил в вашей ситуации:

Если проблема слишком сложна для текущего использования полей JSON, но вам все равно нужно на них положиться:

Используйте сигнал post_save для заполнения таблицы, которую вы ведете, представителем сегмента json-поля, которое вам нужно.

Затем вы можете использовать эту таблицу в операции prefetch/ORM вместо нее.

Вернуться на верх