I’ve got two relations as follows:
class CfThread < ActiveRecord::Base
self.primary_key = 'thread_id'
self.table_name = 'cforum.threads'
belongs_to :forum, class_name: 'CfForum', :foreign_key => :forum_id
has_many :messages, class_name: 'CfMessage', :foreign_key => :thread_id
attr_accessible :thread_id, :tid, :slug, :forum_id, :archived, :created_at, :updated_at
end
class CfMessage < ActiveRecord::Base
has_one :owner, class_name: 'CfUser', :foreign_key => :user_id
has_many :flags, class_name: 'CfFlag'
belongs_to :thread, class_name: 'CfThread', :foreign_key => :thread_id
attr_accessible :message_id, :mid, :thread_id, :subject, :content,
:author, :email, :homepage, :deleted, :user_id, :parent_id,
:updated_at, :created_at
end
I want to query the last 10 threads and its messages without producing a n+1 queries situation:
@threads = CfThread.includes(:messages).order('cforum.threads.created_at DESC').limit(10)
This produces a really slow query using DISTINCT:
SELECT DISTINCT "cforum"."threads".thread_id, cforum.threads.created_at AS alias_0 FROM "cforum"."threads" LEFT OUTER JOIN "cforum"."messages" ON "cforum"."messages"."thread_id" = "cforum"."threads"."thread_id" ORDER BY cforum.threads.created_at DESC LIMIT 10
The query planner want’s to do a full table scan on threads AND messages.
With plain SQL it would be very easy to optimize this situation, but is there a way using AR, too? Can I avoid the DISTINCT without producing n+1 queries?
Greetings,
CK
EDIT: Indexes are given as follows:
Threads:
"threads_pkey" PRIMARY KEY, btree (thread_id)
"index_cforum.threads_on_slug" UNIQUE, btree (slug)
"index_cforum.threads_on_archived" btree (archived)
"index_cforum.threads_on_tid" btree (tid)
"threads_created_at_idx" btree (created_at, updated_at)
Messages:
"messages_pkey" PRIMARY KEY, btree (message_id)
"index_cforum.messages_on_mid" btree (mid)
"index_cforum.messages_on_thread_id" btree (thread_id)
I think your order is confusing active record slightly – I think active record thinks it needs the joined columns in order for the order to work. So it’s using its join based include variant, which in turn requires a distinct query when you want to limit it.
Obviously the order depends only on the CfThread model, it’s just the AR heuristics that are confused. One way would be to force the eager load mode used by doing