Django - набор запросов с множественной фильтрацией возвращает пустой набор запросов

У меня проблема с набором запросов в Django 2.0, после некоторых исследований я не обнаружил, что проблема похожа на мою.

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

Итак, у меня есть база данных sqlite, которая выглядит так:

Как видите, в свойствах таблицы нет primary_key, поэтому я сделал models с командой django inspectdb, которая выглядит так:

from django.db import models

class Record(models.Model):
    id = models.IntegerField(db_column='ID', primary_key=True)

    class Meta:
        db_table = 'Records'

    def __str__(self):
        return "%s" % self.id


class Propertie(models.Model):
    id = models.ForeignKey(Record, models.DO_NOTHING, db_column='ID', primary_key=True)
    item = models.CharField(db_column='Item', max_length=500)
    value = models.CharField(db_column='Value', max_length=500)

    class Meta:
        db_table = 'Properties'

    def __str__(self):
        return '[%s]- %s -> %s' % (self.item, self.value, self.id)

Я установил Properties.id как primary_key, но это ForeignKey, и Джанго говорит установить это поле как OneToOneField, и это нормально и логично, но 1 Record связано с 9 Properties, поэтому Porpertie.id не может быть unique это моя первая проблема, потому что я не могу изменить базу данных.

Моя вторая и реальная проблема возникает, когда я запускаю этот запрос:

def my_view(request):

   epoch = datetime.date(1970, 1, 1)
   period_from = stat_form.cleaned_data.get("period_from")
   period_to = stat_form.cleaned_data.get("period_to")
   product = stat_form.cleaned_data.get("kit")

   timestamp_from = period_from - epoch
   timestamp_to = period_to - epoch

   records = Record.objects.using("statool").filter(
        propertie__item="product",
        propertie__value=product,
    ).filter(
        propertie__item="stamp",
        propertie__value__gt=str(int(timestamp_from.total_seconds())),
        propertie__value__lt=str(int(timestamp_to.total_seconds())),
    ).count()

этот QuerySet пуст, но должен вернуть примерно 16XXX Record Я не знаю, что происходит?

Потому что, если я сделаю этот запрос:

  records = Record.objects.using("statool").filter(
        propertie__item="product",
        propertie__value=product,
  )

Он возвращает результат, но второй фильтр не работает...

Цель этого запроса — получить Record с конкретной датой и названием продукта.

9 возможностей поля item в Properties могут быть:

  • продукт
  • версия
  • инструмент
  • штамп
  • пользователь
  • хозяин
  • сайт
  • проект
  • аргументы

Будущий запрос с той же логикой будет применен сразу после получения версии по продукту и по сайту.

Спасибо за помощь! И извините за мой плохой английский :)


person Florian Couderc    schedule 22.06.2018    source источник
comment
propertie__value__gt=str(int(timestamp_from.total_seconds())), propertie__value__lt=str(int(timestamp_to.total_seconds())), Здесь сравнивается value с продолжительностью времени в секундах. Вы уверены, что value является атрибутом, по которому вы хотите отфильтровать?   -  person iacob    schedule 22.06.2018
comment
Чтобы расширить то, что @ukemi сказал в своем комментарии: при сравнении строк используется лексикографический порядок, поэтому 100 < 90 потому что символ 1 стоит перед 9. Такое поведение, вероятно, не то, что вы ищете.   -  person Ralf    schedule 22.06.2018
comment
Значение @ukemi yes — это атрибут, который я хочу отфильтровать, когда item=="stamp", value равны отметке времени в секундах, но в базе данных они хранятся как TEXT, а не INTEGER. @Ralf Как я могу преобразовать value, если item==stamp в QuerySet? вы правы str(100) < str(90) и я думаю, что это может вызвать некоторые проблемы. Но если я переверну QuerySetи начну с:records=Record.objects.using("statool").filter(propertie__item="stamp", propertie__value__gt=str(int(timestamp_from.total_seconds())), propertie__value__lt=str(int(timestamp_to.total_seconds())),), запрос будет работать, но не если я применю второй   -  person Florian Couderc    schedule 22.06.2018


Ответы (1)


Чтобы ответить на мою проблему,

сначала я остановился, чтобы попробовать пользователя multi .filter, потому что когда я запускаю:

records = Record.objects.using("statool").filter(
    propertie__item="product",
    propertie__value=product,
).filter(
    propertie__item="stamp",
    propertie__value__gt=str(int(timestamp_from.total_seconds())),
    propertie__value__lt=str(int(timestamp_to.total_seconds())),
).count()

После того, как первые объекты .filterRecord потеряли ссылку на propertie_set, я не могу фильтровать по свойствам.

Как говорят @ukemi и @Ralf, используя:

.filter(
    propertie__item="stamp",
    propertie__value__gt=str(int(timestamp_from.total_seconds())),
    propertie__value__lt=str(int(timestamp_to.total_seconds())),
)

это действительно плохая идея иметь точный запрос.

Итак, это мое решение:

def select_stats(request):
    epoch = datetime.date(1970, 1, 1)
    period_from = stat_form.cleaned_data.get("period_from")
    period_to = stat_form.cleaned_data.get("period_to")
    product = stat_form.cleaned_data.get("kit")

    timestamp_from = period_from - epoch
    timestamp_to = period_to - epoch
    timestamp_from = int(timestamp_from.total_seconds())
    timestamp_to = int(timestamp_to.total_seconds())

    all_product = Propertie.objects.using("statool").filter(
        item="product",
        value=product
    ).values_list("id", flat=True)

    all_stamp = Propertie.objects.using("statool").annotate(
        date=Cast("value", IntegerField())
    ).filter(
        date__gte=timestamp_from,
        date__lt=timestamp_to
    ).values_list("id", flat=True)

    all_records = Record.objects.using("statool").filter(
        id__in=all_product.intersection(all_stamp)
    )

    all_recorded_propertie = Propertie.objects.using("statool").filter(id__in=all_records)

    all_version = all_recorded_propertie.filter(
        id__in=all_records,
        item="version"
    ).values_list("value", flat=True).distinct()

    all_site = all_recorded_propertie.filter(
        id__in=all_records,
        item="site"
    ).values_list("value", flat=True).distinct()

    stats_site = {}
    for version in all_version:
        stats_site[version] = {}
        id_version = all_recorded_propertie.filter(
            item="version",
            value=version
        ).values_list("id", flat=True)
        for site in all_site:
            id_site = all_recorded_propertie.filter(
                item="site", 
                value=site
            ).values_list("id", flat=True)
            stats_site[version][site] = id_version.intersection(id_site).count()

Чтобы решить проблему с отметкой времени таким образом:

all_stamp = Propertie.objects.using("statool").annotate(
    date=Cast("value", IntegerField())
).filter(
    date__gte=timestamp_from,
    date__lt=timestamp_to
).values_list("id", flat=True)

Спасибо @erikreed из этой темы: Приведение Django QuerySet

Между прочим, это самый эффективный способ выполнения моей работы, который я нашел.
Но если мы запустим это представление, у нас будет такая среда выполнения: просмотреть время выполнения запроса

Как видите, каждый QuerySet очень быстрый, но пересечения между version.id и site.id очень длинные (более 2 минут).

Если кто-то знает лучший способ сделать этот запрос, просто дайте нам знать :)
Надеюсь, я помог кому-то.

person Florian Couderc    schedule 25.06.2018