示例:
Foobar.joins(:baz).includes(:baz).count
=> 22926
Foobar.joins(:baz).includes(:baz).find_each.count
=> 998
Foobar.joins(:baz).find_each.count
=> 22926
正确情况下生成的sql(第三个)是几批sql,如下所示:
SELECT "foobar".* FROM "foobar" INNER JOIN "baz" ON
"baz"."foobar_id" = "foobar"."id" ORDER BY "foobar"."id" ASC LIMIT $1
在失败的(第二个)情况下,有一个看起来像的查询:
SELECT "foobar"."id" AS t0_r0
"baz"."id" AS t1_r0
"baz"."foobar_id" AS t1_r1
FROM "foobar" INNER JOIN "baz" ON "baz"."foobar_id" = "foobar"."id"
ORDER BY "foobar"."id" ASC LIMIT $1
其中所有字段都列为每个表上不同列的不同临时变量(例如t0_r0
)(在实际查询中,第一个对象上有37个拆分30,第二个上有7个) 。
这是一个错误吗? includes
查询中不允许find_each
?我做错了吗?
Foobar
和Baz
之间的关系为Foobar
has_one
Baz
和Baz
belongs_to
Foobar
。
答案 0 :(得分:3)
如果您的has_one
关系不是has_one
,则会出现此问题。
假设您的数据库在列baz.foobar_id
上没有唯一索引。然后你可能会意外地结束这样的情况,你有一个连接到多个Baz记录的Foobar记录:
baz.id | baz.foobar_id
------ -------------
1 1
2 1
3 2
在这种情况下,joins
将返回Foobar和Baz记录的组合:
Foobar.joins(:baz).count # This would be 3
这也意味着find_each
join
会迭代3次并重复其中一个Foobar ID:
Foobar.joins(:baz).find_each(batch_size: 2) { |f| puts f.id }
# SELECT "foobar".* FROM "foobar" INNER JOIN "baz" ON... LIMIT 2
1
1
# SELECT "foobar".* FROM "foobar" INNER JOIN "baz" ON... WHERE ("foobar"."id" > 1) ... LIMIT 2
2
添加includes
意味着Rails将尝试将结果合并回一组不同的Foobar记录。但这不适用find_each
管理其批次的方式:
Foobar.joins(:baz).includes(:baz).find_each(batch_size: 2) { |f| puts f.id }
# SELECT "foobar"."id" AS t0_r0 ... LIMIT 2
1
此时find_each
将stop processing,因为它发现早期批次小于批量大小,因此它认为已完成:
# ActiveRecord::Batches#in_batches
break if ids.length < batch_limit
find_each
的默认批量大小为1,000。您的问题案例返回了998条记录。这表明第一批它加载了998个独特的Foobar ID,小于批量大小,find_each
认为它已经完成。它可能装载了1,000个Baz记录,这些记录与998个不同的Foobar记录相关联。
您可能需要查看baz
表,看看它是否有任何重复的条目。您可以使用以下内容执行此操作:
Baz.group(:foobar_id).having('count(*) > 1')
最佳解决方案是使用唯一索引来避免数据库中的重复并强制执行has_one
关系。另一种方法是确保您获得一组不同的Foobar记录,例如:
Foobar.group(:id).joins(:baz).includes(:baz).count
Foobar.group(:id).joins(:baz).includes(:baz).find_each.count
Foobar.group(:id).joins(:baz).find_each.count