Вложение наборов запросов Django
Есть ли способ создать кверисет, который работает с вложенным кверисетом?
Самый простой пример, который я могу придумать, чтобы объяснить, чего я пытаюсь достичь, это демонстрация.
Я хотел бы написать код примерно такого вида
SensorReading.objects.filter(reading=1).objects.filter(meter=1)
в результате SQL выглядит как
SELECT * FROM (
SELECT * FROM SensorReading WHERE reading=1
) WHERE sensor=1;
Более конкретно, у меня есть модель, представляющая показания датчиков
class SensorReading(models.Model):
sensor=models.PositiveIntegerField()
timestamp=models.DatetimeField()
reading=models.IntegerField()
С помощью этого я создаю набор запросов, который аннотирует каждый датчик с прошедшим временем с предыдущего показания в секундах
readings = (
SensorReading.objects.filter(**filters)
.annotate(
previous_read=Window(
expression=window.Lead("timestamp"),
partition_by=[F("sensor"),],
order_by=["timestamp",],
frame=RowRange(start=-1, end=0),
)
)
.annotate(delta=Abs(Extract(F("timestamp") - F("previous_read"), "epoch")))
)
Теперь я хочу объединить их по каждому датчику, чтобы увидеть минимальное и максимальное время, прошедшее между показаниями каждого датчика. Сначала я пытался
readings.values("sensor").annotate(max=Max('delta'),min=Min('delta'))[0]
однако это не удается, поскольку значения окон не могут быть использованы внутри агрегата.
Есть ли методы или библиотеки для решения этой проблемы без необходимости прибегать к сырому SQL? Или я просто упустил из виду более простое решение проблемы?
Краткий ответ: Да можно, используя id__in lookup и подзапрос в методе filter. функция из django.db.models модуля.
Длинный ответ - как? :
Вы можете создать подзапрос, который извлекает отфильтрованные объекты SensorReading, а затем использовать этот подзапрос в основном наборе запросов (Например):
from django.db.models import Subquery
subquery = SensorReading.objects.filter(reading=1).values('id')
readings = SensorReading.objects.filter(id__in=Subquery(subquery), meter=1)
Приведенный выше код сгенерирует SQL, похожий на тот, что вы описали в своем примере:
SELECT * FROM SensorReading
WHERE id IN (SELECT id FROM SensorReading WHERE reading=1)
AND meter=1;
Другой способ заключается в цепочке filter() на queryset, который вы создали, и добавлении второго фильтра поверх него
readings = (
SensorReading.objects.filter(**filters)
.annotate(
previous_read=Window(
expression=window.Lead("timestamp"),
partition_by=[F("sensor"),],
order_by=["timestamp",],
frame=RowRange(start=-1, end=0),
)
)
.annotate(delta=Abs(Extract(F("timestamp") - F("previous_read"), "epoch")))
.filter(sensor=1)
)
В итоге я создал свое собственное решение, в основном интроспекция набора запросов для создания поддельной таблицы для использования в создании нового набора запросов и установки псевдонима узлу, который знает, как вывести SQL для внутреннего запроса
позволяет мне сделать что-то вроде
readings = (
NestedQuery(
SensorReading.objects.filter(**filters)
.annotate(
previous_read=Window(
expression=window.Lead("timestamp"),
partition_by=[F("sensor"),],
order_by=[
"timestamp",
],
frame=RowRange(start=-1, end=0),
)
)
.annotate(delta=Abs(Extract(F("timestamp") - F("previous_read"), "epoch")))
)
.values("sensor")
.annotate(min=Min("delta"), max=Max("delta"))
)
код доступен на github, и я опубликовал его на pypi
https://github.com/Simage/django-nestedquery
Я не сомневаюсь, что я сливаю таблицы или еще какую-нибудь подобную ерунду, и это должно считаться доказательством концепции, а не каким-то производственным кодом.