Руслан Волшебник
Руслан Волшебник13 декабря 2019 г. 7:53

Как уменьшить количество запросов в Django ORM?

Django, ORM, DataBase

Доброго времени суток.

Задача такая.
Нужно получить QuerySet, который состоит из вопросов разных категорий. Вопросы должны быть разной сложности, а для каждой сложности своё количество. Все вопросы нужно вытащить из бд в случайном порядке.

Есть вот такой код.

# models.py
class Question(models.Model):
    EASY = 1
    MEDIUM = 2
    HARD = 3

    DIFFICULTY_CHOICES = (
        (EASY, 'Легкий'),
        (MEDIUM, 'Средний'),
        (HARD, 'Сложный')
    )

    text = models.TextField(max_length=300, null=True, verbose_name='текст вопроса')
    categories = models.ManyToManyField(Category, related_name="questions", verbose_name='категории')
    difficulty = models.PositiveSmallIntegerField(choices=DIFFICULTY_CHOICES, default=MEDIUM, verbose_name='сложность')
questions = None

for category in categories:
    category_id = category['id']

    easy_question_count = category['easy_question_count']
    medium_question_count = category['medium_question_count']
    hard_question_count = category['hard_question_count']

    easy_questions = Question.objects.filter(categories__id=category_id, difficulty=1)
    medium_questions = Question.objects.filter(categories__id=category_id, difficulty=2)
    hard_questions = Question.objects.filter(categories__id=category_id, difficulty=3)

    q1 = easy_questions.objects.random(easy_question_count)
    q2 = medium_questions.objects.random(medium_question_count)
    q3 = hard_questions.objects.random(hard_question_count)

    if questions:
        questions = questions.union(q1).union(q2).union(q3)
    else:
        questions = q1.union(q2).union(q3)

Метод random взят из библиотеки https://github.com/rremizov/django-random-queryset.git
Это библиотека позволяет получить QuerySet определенной длины, состоящий из элементов в случайном порядке.

Вот скрин запросов.

Это для одной категории, а если категорий, к примеру 10, то получится 60 запросов.
Как это дело оптимизировать? Ломаю голову 3 дня.

Может кто-нибудь подсказать, как мне решить задачу?

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

9
Evgenii Legotckoi
  • 13 декабря 2019 г. 9:00

Добрый день,

Я правильно понимаю, что вы в итоге сваливаете в кучу все вопросы в объект questions?

Возможно, что стоит это переписать иначе, например так

questions = None

easy_questions = Question.objects.filter(categories__in=categories, difficulty=1)
medium_questions = Question.objects.filter(categories__in=categories, difficulty=2)
hard_questions = Question.objects.filter(categories__in=categories, difficulty=3)

q1 = easy_questions.objects.random(easy_questions.count())
q2 = medium_questions.objects.random(medium_questions.count())
q3 = hard_questions.objects.random(hard_questions.count())

questions = q1.union(q2).union(q3)
    Evgenii Legotckoi
    • 13 декабря 2019 г. 9:02

    Ну а если вам нужно просто срандомизировать всё в одной куче, то я бы так попробовал написать

    questions = Question.objects.filter(categories__in=categories)
    questions = questions.objects.random(questions.count())
    
      Руслан Волшебник
      • 13 декабря 2019 г. 9:19

      -"Я правильно понимаю, что вы в итоге сваливаете в кучу все вопросы в объект questions?"
      -Да. Это для дальнейших манипуляций) Возможно, переделаю.

      Проблема в том, что для каждой категории нужно разное кол-во вопросов.
      Например:
      Категория биология, 1 легкий вопрос, 2 средних и 3 сложных;
      Категория математика, 2 легких, 2 средних и 2 сложных....и т.д.

        Илья Чичак
        • 13 декабря 2019 г. 9:26
        • (ред.)

        Кстати, сделать случайный порядок можно крайне просто:

        Question.objects.all().order_by('?')
        

        Документация

        Вообще, я слабо понял задачу. Если чуть более понятно сможете описать ее, возможно, смогу помочь

          Руслан Волшебник
          • 13 декабря 2019 г. 9:42
          • (ред.)

          На stackoverflow пишут, что это дорогая операция и лучше по-другому.
          В доках пишут:
          Note: order_by('?') queries may be expensive and slow, depending on the database backend you’re using.

          Я конечно не спец(всего лишь любитель), но я попробую таким способ тоже, может в моем случае будет всё норм.

          На счёт задачи.
          Есть, к примеру, категории: биология, математика, физика...и т.д.
          В каждой категории есть вопросы. У всех вопросов есть сложность от 1 до 3.

          Нужно вот что.
          Из категории физика нужно достать в случайном порядке 3 легких вопроса, 2 средних и 1 сложный;
          Из категории математика 1 легкий, 3 средних, 2 сложных. Тоже в случайном порядке .

          Моя проблема в том, что чем больше категорий, тем больше запросов.
          Если нужно сделать поиск вопросов только по 1-ой категории, то 6 запросов, по 2-ум категориям 12 запросов и т.д.

          Хотелось бы, чтобы при увеличении количества категорий, количество запросов не увеличивалось.

            Evgenii Legotckoi
            • 13 декабря 2019 г. 9:59

            Руслан, посмотрите исходники той библиотеки, которую вы использовали. Там как раз и используется order_by('?') в конечном итоге, а ещё перед этим используется агрегация id всех объектов в queryset. Метод aggregate , кстати, тоже достаточно дорогой, я стараюсь избегать его или кешировать.

            Так что в методе random , который выполняет данная библиотека используется сразу два дорогостоящих запроса, хорошо хоть там нет distinct , иначе совсем вилы были бы.

            Я вашу задачу понял, но вопрос действительно сложный, пока мыслей нет.

              Evgenii Legotckoi
              • 13 декабря 2019 г. 10:02

              К слову говоря, этот метод order_by('?') мне кажется относительно рандомным, поскольку он выполняет сортировку по случайному столбцу и в случайном направлении (по алфавиту/ против алфавита), если я правильно понял его суть. В общем он настолько рандомный, сколько у вас полей в модели.

                Руслан Волшебник
                • 13 декабря 2019 г. 10:16
                • (ред.)

                Да, я посмотрел, вы абсолютно правы. Единственное, если я правильно понял, если срабатывает условие

                if (aggregates["max_id"] - aggregates["min_id"]) + 1 == aggregates["count"]:
                    return self.filter(
                        id__in=strategies.min_max(
                            amount,
                            aggregates["min_id"],
                            aggregates["max_id"],
                            aggregates["count"],
                        )
                    )
                

                то он возвращает QuerySet минуя order_by('?').

                  Руслан Волшебник
                  • 15 декабря 2019 г. 4:38
                  • (ред.)

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

                  И да, после использования union, нельзя юзать filter, а так как мне нужно потом отфильтровать ещё раз, то я сделал так

                  easy_question_count = quiz.easy_question_count
                  medium_question_count = quiz.medium_question_count
                  hard_question_count = quiz.hard_question_count
                  
                  easy_questions = questions.filter(difficulty=1)
                  medium_questions = questions.filter(difficulty=2)
                  hard_questions = questions.filter(difficulty=3)
                  
                  easy_questions = get_random_questions(easy_questions, easy_question_count)
                  medium_questions = get_random_questions(medium_questions, medium_question_count)
                  hard_questions = get_random_questions(hard_questions, hard_question_count)
                  
                  question_ids = easy_questions.union(medium_questions).union(hard_questions).values_list('id', flat=True)
                  questions = questions.filter(id__in=question_ids)
                  
                  

                  А для получения рандомных вопросов нужного мне количества написал вот такую функцию.

                  def get_random_questions(questions, question_count):
                      question_id_array = list(questions.values_list('id', flat=True))
                      random_ids = random.sample(question_id_array, min(len(question_id_array), question_count))
                      questions = questions.filter(id__in=random_ids)
                      return questions
                  

                  Получилось всего 5 запросов.

                  Готов выслушать критику, если есть)

                    Комментарии

                    Только авторизованные пользователи могут публиковать комментарии.
                    Пожалуйста, авторизуйтесь или зарегистрируйтесь
                    AD

                    C++ - Тест 004. Указатели, Массивы и Циклы

                    • Результат:50баллов,
                    • Очки рейтинга-4
                    m
                    • molni99
                    • 26 октября 2024 г. 1:37

                    C++ - Тест 004. Указатели, Массивы и Циклы

                    • Результат:80баллов,
                    • Очки рейтинга4
                    m
                    • molni99
                    • 26 октября 2024 г. 1:29

                    C++ - Тест 004. Указатели, Массивы и Циклы

                    • Результат:20баллов,
                    • Очки рейтинга-10
                    Последние комментарии
                    i
                    innorwall11 ноября 2024 г. 22:12
                    Django - Урок 055. Как написать функционал auto populate field Freckles because of several brand names retin a, atralin buy generic priligy
                    i
                    innorwall11 ноября 2024 г. 18:23
                    QML - Урок 035. Использование перечислений в QML без C++ priligy cvs 24 Together with antibiotics such as amphotericin B 10, griseofulvin 11 and streptomycin 12, chloramphenicol 9 is in the World Health Organisation s List of Essential Medici…
                    i
                    innorwall11 ноября 2024 г. 15:50
                    Qt/C++ - Урок 052. Кастомизация Qt Аудио плеера в стиле AIMP It decreases stress, supports hormone balance, and regulates and increases blood flow to the reproductive organs buy priligy online safe Promising data were reported in a PDX model re…
                    i
                    innorwall11 ноября 2024 г. 14:19
                    Алгоритм сортировки кучей The role of raloxifene in preventing breast cancer priligy precio
                    i
                    innorwall11 ноября 2024 г. 13:55
                    PyQt5 - Урок 006. Работа с QTableWidget buy priligy 60 mg 53 have been reported by Javanovic Santa et al
                    Сейчас обсуждают на форуме
                    i
                    innorwall11 ноября 2024 г. 20:56
                    добавить qlineseries в функции buy priligy senior brother Chu He, whom he had known for many years
                    i
                    innorwall11 ноября 2024 г. 10:55
                    Всё ещё разбираюсь с кешем. priligy walgreens levitra dulcolax carbs The third ring was found to be made up of ultra relativistic electrons, which are also present in both the outer and inner rings
                    9
                    9Anonim25 октября 2024 г. 9:10
                    Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…
                    ИМ
                    Игорь Максимов3 октября 2024 г. 4:05
                    Реализация навигации по разделам Спасибо Евгений!

                    Следите за нами в социальных сетях