按子属性过滤父级,但是急切加载所有子级

时间:2013-07-25 14:46:42

标签: ruby-on-rails activerecord

这个标题有点迟钝,所以这是一个例子。假设我们有一个包含模型ShipPirateParrot的Rails 3应用。一艘船上有很多海盗,一个海盗有很多鹦鹉。

Ship.includes(pirates: :parrots).where('parrots.name LIKE ?', '%polly%')

这会使至少有一名海盗的船只至少有一只鹦鹉,其名字就像是“polly”。我还希望为这些船只急切地加载所有海盗和鹦鹉 ......但实际上只有配有鹦鹉的海盗才会急切加载,其中只有匹配的鹦鹉渴望加载。生成的SQL是这样的:

SELECT ships.id AS t0_r0, ships.name AS t0_r1, pirates.id AS t1_r0, pirates.name AS t1_r1, parrots.id AS t2_r0, parrots.name AS t2_r1 FROM ships LEFT OUTER JOIN pirates ON pirates.ship_id = ships.id LEFT OUTER JOIN parrots ON parrots.pirate_id = pirates.id WHERE (parrots.name LIKE '%polly%')

在没有条件的情况下执行Ship.includes(pirates: :parrots)时,ActiveRecord会生成一组更接近我想要的查询:

SELECT ships.* FROM ships
SELECT pirates.* FROM pirates WHERE pirates.ship_id IN (ship IDs from previous query)
SELECT parrots.* FROM parrots WHERE parrots.pirate_id IN (pirate IDs from previous query)

如果我可以以某种方式更改第一个查询以使用第一个示例中的SQL,那么它将完全符合我的要求:

SELECT ships.* FROM ships LEFT OUTER JOIN pirates ON pirates.ship_id = ships.id LEFT OUTER JOIN parrots ON parrots.pirate_id = pirates.id WHERE (parrots.name LIKE '%polly%')
SELECT pirates.* FROM pirates WHERE pirates.ship_id IN (ship IDs from previous query)
SELECT parrots.* FROM parrots WHERE parrots.pirate_id IN (pirate IDs from previous query)

但是我不知道有什么方法可以让ActiveRecord这样做,或者自己做任何方式并“手动”连接预先加载(这在我的情况下是必要的,以避免N + 1查询爆炸)。任何想法或建议将不胜感激。

2 个答案:

答案 0 :(得分:1)

Ship.joins(pirates: :parrots).where('parrots.name LIKE ?', '%polly%').preload(pirates: :parrots)

需要rails 3 +

答案 1 :(得分:0)

如果您正在寻找INNER JOIN,我想

Ship.includes(pirates: :parrots).where('parrots.name LIKE ?', '%polly%').joins(pirates: :parrots)

完成它。