简体   繁体   中英

Avoiding N+1 queries in a Rails multi-table query

This is the query I've got at present:

SELECT t1.discipline_id AS discipline1,
  t2.discipline_id AS discipline2,
  COUNT(DISTINCT t1.product_id) as product_count
FROM (SELECT "product_disciplines".* FROM "product_disciplines") t1
INNER JOIN (SELECT "product_disciplines".* FROM "product_disciplines") t2
ON t1.product_id = t2.product_id
WHERE (t1.discipline_id < t2.discipline_id)
GROUP BY t1.discipline_id, t2.discipline_id
ORDER BY "product_count" DESC

Basically, I've got a list of Products and Disciplines, and each Product may be associated with one or more Disciplines. This query lets me figure out, for each possible (distinct) pair of disciplines, how many products are associated with them. I'll use this as input to a dependency wheel in Highcharts .

The problem arises when I involve Active Model Serializers. This is my controller:

class StatsController < ApplicationController
  before_action :get_relationships, only: [:relationships]

  def relationships
    x = @relationships
      .select('t1.discipline_id AS discipline1, t2.discipline_id AS discipline2, COUNT(DISTINCT t1.product_id) as product_count')
      .order(product_count: :DESC)
      .group('t1.discipline_id, t2.discipline_id')
    render json: x, each_serializer: RelationshipSerializer
  end

  private

  def get_relationships
    query = ProductDiscipline.all

    @relationships = ProductDiscipline
      .from(query, :t1)
      .joins("INNER JOIN (#{query.to_sql}) t2 on t1.product_id = t2.product_id")
      .where('t1.discipline_id < t2.discipline_id')
  end
end

each_serializer points to this class:

class RelationshipSerializer < ApplicationSerializer
  has_many :disciplines do
    Discipline.where(id: [object.discipline1, object.discipline2])
  end
  attribute :product_count
end

When I query the database, there are ~1300 possible pairs, which translates my single query in ~1300 Discipline lookups.

Is there a way to avoid the N+1 queries problem with this structure?

From which model this RelationshipSerializer is called? I don't understand why do you need to use object.disciplne1 and object.discipline2 when you could just have a column in the Discipline model pointing out to that model. And in the serializer you would use:

has_many :disciplines, class_name: "::Discipline"

And before you call this serializer make sure that your activerecord query uses includes(:disciplines) for eager loading .

I ended up splitting this in two separate API queries. RelationshipSerializer saves just the discipline IDs,

class RelationshipSerializer < ApplicationSerializer
  # has_many :disciplines do
  #   # Discipline.where(id: [object.discipline1, object.discipline2])
  #   [object.discipline1, object.discipline2].to_json
  # end
  attributes :discipline1, :discipline2
  attribute :product_count
end

Since in my app I already need the list of available disciplines, I chose to correlate them client-side.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM