Rails ActiveRecord加入并包含在同一个查询中?

时间:2015-12-03 02:35:55

标签: mysql ruby-on-rails activerecord

我一直认为我应该使用.joins.includes,但不能同时使用同一查询。

假设我们在数据库中有一个包含3个表的模式。

我有一个User型号。

User has_many Contact

Contact has_many ContactEvent

请注意,有些Contacts有0 Contactevents,有些有很多!

目标

我想获得一个特定的User,获取他的每个Contacts并计算每个ContactEvent有多少Contact

首次尝试是这样的:

user = User.find_by_id(1)

user.contacts.map{|contact| contact.contact_events.size}

这会产生大量的查询并对内存造成巨大负担(它必须加载用户在内存中的所有50k联系人,然后为每个联系人启动查询)。

性能非常差,因为它加载了所有Contacts,完全没有被要求,因为,例如,来自50k Contacts只有3k的ContactEvents

第二次尝试:

Contact.joins(:contact_events).where("user_id = 1").uniq.map{|c| c.contact_events.size}

这个速度要快得多,因为它首先只加载已经有Contacts的{​​{1}},但仍然需要10秒钟,并且控制台中有大量查询试图获取{{每个ContactEvents 1}}。示例:contact_events

第三是魅力......但它是否正确?

由于我注意到之前的尝试是针对每个:contact_events进行额外查询,因此我提示使用contact

但如果我只是将(7.0ms) SELECT COUNT(*) FROM contact_events WHERE contact_events.contact_id = 2052447替换为.includes,我发现它不会仅返回.joins .includes,而是会返回所有Contacts

我终于通过使用以下行获得了我想要的东西:

ContactEvents

这是超快的,只有一个查询,需要不到一秒钟。

这是正确的做法吗?对同一个查询使用.joins和.includes吗?

1 个答案:

答案 0 :(得分:0)

您可以选择更有效地计算contact_events

选项1:

使用LEFT {OUTER} JOIN表示如果contact没有contact_events则会显示0.当您认为要消失{{1}时,可以使用INNER JOIN当没有contact时。

contact_events

选项2:

您可以使用counter_cache

Contact.select("contacts.*, COUNT(contact_events.contact_id) as contact_events_count")
.join("LEFT JOIN contact_events ON contacts.id = contact_events.contact_id")
.group("contacts.id")

最后,要获得专柜大小,您可以致电:

<强>控制器

# Contact
has_many :contact_events

# Contact Event
belongs_to :contact, counter_cache: true

查看

@contacts = Contact.all

我希望这对你有所帮助。