首页 > 解决方案 > 我可以根据没有匹配项创建 Active Record 关系吗

问题描述

我正在尝试加快具有大量 n+1 查询的 rails 应用程序上的索引页面。

目前我有一种方法可以基本上颠倒关系(找到与当前无关的记录)

Class Person < ActiveRecord::Base
    has_many_and_belongs_to_many :bankholidays, join_table: “working_bankholidays”

    def free_bankholidays
        Bankholiday.where(“id NOT IN (?)” bankholiday_ids)
    end
end
Class Bankholiday < ActiveRecord::Base
    has_many_and_belongs_to_many :persons, join_table: “working_bankholidays”
end
Class WorkingBankholiday < ActiveRecord::Base
    belongs_to :person
    belongs_to :bankholiday
end

Person 中的 free_bankholidays 方法返回与当前 Person 无关的任何 Bankholidays。如果我正在加载包含多人的索引页面,这可以正常工作,但会减慢速度。有没有办法可以将其转移到关系中,以便可以急切地加载?

标签: ruby-on-railsrubyactiverecord

解决方案


您需要的是一个外连接,并选择外连接创建 NULL 的结果。

您需要的 SQL 是:

SELECT bankholidays.*
FROM bankholidays
  LEFT OUTER JOIN working_bankholidays on bankholidays.id = working_bankholidays.bankholiday_id
  AND working_bankholidays.person_id = 111
WHERE working_bankholidays.id is NULL

这里的问题是您需要person_idinFROM子句的条件。如果它在WHERE子句中,您将得到不同的结果。

当它在 FROM 子句中时,它将使用条件将working_bankholidays表视为只有匹配的行person_id。它在执行外连接之前执行此操作,因此如果没有匹配的记录,person_id它将添加来自外连接的记录,working_bankholidays表的所有字段中的值为 NULL。

不幸的是,我不知道有什么方法可以通过活动记录关联来做到这一点。所有活动记录关联都将条件放入WHERE子句,而不是FROM子句。

如果你改变has_many_and_belongs_to_manyto 有很多通过,你这样做:

class Person
  def free_bankholidays
    Bankholiday.left_joins(:working_bankholidays).where(working_bankholidays: {id: nil, person_id: self.id})
  end

它会给你这个:

SELECT bankholidays.*
FROM bankholidays
  JOIN working_bankholidays on bankholidays.id = working_bankholidays.bankholiday_id
WHERE working_bankholidays.id is NULL
  AND working_bankholidays.person_id = 111

这会给你不同的和不正确的结果。

编辑:

请参阅engineersmnky 的评论。我还没有尝试过,所以我还没有准备好将它包含在我的答案中。


推荐阅读