В настоящее время у меня есть запрос контроллера, который выбирает продукты и обновления продуктов следующим образом:

products = Product.left_outer_joins(:productupdates).select("products.*, count(productupdates.*) as update_count, max(productupdates.old_price) as highest_price").group(:id)
products = products.paginate(:page => params[:page], :per_page => 20)

Этот запрос создает N + 1 запрос, но я не могу поставить .include (: productsupdates), так как у меня также есть оставленное соединение.

Если возможно, не могли бы вы помочь мне, как уменьшить количество запросов N + 1?

< Сильный > ИЗМЕНИТЬ ------------------------------ Согласно предложению Вишала; Я изменил запрос контроллера следующим образом:

products = product.includes(:productupdates).select("products.*, count(productupdates.*) as productupdate_count, max(productupdates.old_price) as highest_price").group("productupdates.product_id")
products = products.paginate(:page => params[:page], :per_page => 20)

К сожалению, я получаю следующую ошибку:

ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "productupdates"
LINE 1: SELECT  products.*, count(productupdates.*) as productupdate_count, m...
                               ^
: SELECT  products.*, count(productupdates.*) as productupdate_count, max(productupdates.old_price) as highest_price FROM "products" WHERE "products"."isopen" = $1 AND (products.year > 2009) AND ("products"."make" IS NOT NULL) GROUP BY productupdates.product_id LIMIT $2 OFFSET $3):
1
Tolga 17 Май 2019 в 12:40

2 ответа

Лучший ответ

Пожалуйста, сообщите, как это вызывает N + 1 и как, по вашему мнению, это решит проблему. Единственный способ увидеть ситуацию N + 1 - это если вы потом вызываете productupdates для каждого продукта. Если это так, то это не решит проблему. Посоветуйте, пожалуйста, чтобы другие могли сформулировать соответствующие ответы

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

@products = Product.eager_load(:productupdates) 

Теперь, когда мы зациклимся, productupdates уже загружены, чтобы получить количество и максимум, мы можем сделать что-то вроде

@products.each do |p| 
  # COUNT 
  # (don't use the count method or it will execute a query )
  p.productupdates.size 
  # MAX old_price
  # older ruby versions use rails `try` instead 
  # e.g. p.productupdates.max_by(&:old_price).try(:old_price) || 0
  p.productupdates.max_by(&:old_price)&.old_price || 0
end

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

Примечание: . Причина, по которой includes не сработал, заключается в том, что includes будет использовать 2 запроса для извлечения данных (внешнее соединение sudo), если не выполнено одно из следующих условий :

  • В предложении where используется условие поиска хеша, которое ссылается на таблицу ассоциаций (например, where(productupdates: {old_price: 12}))
  • Вы включаете метод references (например, Product.includes(:productupdates).references(:productupdates))

В обоих этих случаях таблица будет оставлена объединенной. В любом случае я решил использовать энергичную нагрузку в качестве includes делегатов eager_load в вышеупомянутых случаях.

2
engineersmnky 17 Май 2019 в 15:37

Вы можете напрямую сделать Product.includes(:productupdates), чтобы выполнить запрос к базе данных с помощью левого внешнего соединения, а также решить проблему запроса N + 1.

Поэтому вместо Product.left_outer_joins(:productupdates) в своем запросе используйте Product.includes(:productupdates)

После запуска этого запроса в консоли вы можете видеть, что includes запускает запрос левого внешнего соединения к таблице

-1
Vishal 17 Май 2019 в 11:14