Отвечают ли .select и / или .where за создание N + 1 запросов в рельсах? - PullRequest
0 голосов
/ 03 февраля 2019

У меня есть два метода здесь: Different_question_ids и @correct_on_first попытка.Цель состоит в том, чтобы показать пользователю, на сколько правильных вопросов с несколькими вариантами ответов были даны правильные ответы.

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

Теперь, когда пользователь отвечает на тысячи вопросов и имеет тысячи пользовательских ответов, страница, на которой отображается его производительность, загружается от 30 секунд до минуты.И я полагаю, что это из-за метода .select, но я не знаю, как заменить .select без использования .select, поскольку он зацикливается так же, как .each

Есть ли какой-либо метод, который не вызывает N+1?

distinct_question_ids = @user.user_answers.includes(:multiple_choice_question).
  where(is_correct_answer: true).
  distinct.pluck(:multiple_choice_question_id)

@correct_on_first_attempt = distinct_question_ids.select { |qid|
  @user.user_answers.
    where(multiple_choice_question_id: qid).first.is_correct_answer
}.count

1 Ответ

0 голосов
/ 03 февраля 2019

.pluck возвращает массив значений, а не ActiveRecord :: Relation.

Так что когда вы делаете distinct_question_ids.select, вы не вызываете select ActiveRecord, а select массива.В рамках этого выбора вы запускаете новый новый запрос к @user для каждого только что набранного идентификатора - включая те, которые отклоняются в выборе.

Вы можете создать запрос с именем distinct_questions, который возвращаетсвязь (без отрыва!), а затем создайте correct_on_first_attempt из этого, и я думаю, что вы избежите N + 1 запросов.

Что-то вроде этого:

class UserAnswer < ActiveRecord::Base
   scope :distinct_correct, -> { includes(:multiple_choice_question)
                                   .where(is_correct_answer: true).distinct }

   scope :first_attempt_correct, -> { distinct_correct
                                        .first.is_correct_answer }
end

class User < ActiveRecord::Base
  def good_guess_count
    @correct_on_first_attempt = @user.user_answers.distinct_correct.first_attempt_correct.count
  end
end

Вам нужно убедиться, что .first действительно получает свою первую попытку, возможно, путем сортировки по id или create_at.

В качестве отступления, если вы явно отслеживаете номер попытки в UserAnswer, вы действительно можете это сузить:

class UserAnswer < ActiveRecord::Base
  scope :correct, -> { where(is_correct_answer: true) }
  scope :first_attempt, -> { where(attempt: 1) }
end

class User < ActiveRecord::Base
  def lucky_guess_count
    @correct_on_first_attempt = @user.user_answers.includes(:multiple_choice_question)
                                     .correct.first_attempt.count
  end
end

Если у вас нет номера попытки в вашей схеме, вы можете .order и .group получить что-то подобное.Но ... кажется, что некоторые требования вашего проекта зависят от этого порядкового номера, поэтому я рекомендую добавить его, если у вас его еще нет.

пс.Для борьбы с N + 1 запросами используйте gem bullet.Это на месте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...