Вложение наборов запросов 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

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

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