Как добавить префетч (или что-то подобное) в кверисет 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-поля - это костыль, на который люди опираются, потому что не могут потрудиться разработать правильное моделирование.
- Денормализуйте ваши json-данные в отдельные таблицы.
- Используйте историю django/другие встроенные/третьи сторонние инструменты для исторического отслеживания.
Json-поля запутают вашу логику и сделают все нерабочим. Уже второй раз я работаю над проектом, в котором инженеры думали, что это элегантно или просто, а в итоге потратили полгода или больше времени, чтобы исправить спагетти, вызванное ими.
Что касается предварительной выборки, вы можете использовать аннотации и подзапросы, однако все, что вы попытаетесь сделать с json-полями , будет дорогостоящим. В частности, потому что вы больше не сможете использовать преимущества операций базы данных, таких как объединения/индексированный поиск и т. д...
Итак, как бы я поступил в вашей ситуации:
Если проблема слишком сложна для текущего использования полей JSON, но вам все равно нужно на них положиться:
Используйте сигнал post_save для заполнения таблицы, которую вы ведете, представителем сегмента json-поля, которое вам нужно.
Затем вы можете использовать эту таблицу в операции prefetch/ORM вместо нее.