Sql 如何跨Rails4中的多个表构建高效的select命令?

Sql 如何跨Rails4中的多个表构建高效的select命令?,sql,ruby-on-rails,postgresql,ruby-on-rails-4,Sql,Ruby On Rails,Postgresql,Ruby On Rails 4,试图找出一种有效的方法来选择跨多个表具有属性的记录。以下是基本设置: 结构 植物(字段:id、名称、位置、颜色)(1000条记录) 名称(字段:id、通用名称)(50条记录) 位置(字段:id、床名)(125条记录) 型号 植物-属于名称,属于位置 名字-有很多植物吗 位置-有很多工厂吗 我的目标是输出侧码中每一朵玫瑰的列表,并显示颜色,但我被select命令卡住了。如果我得到所有植物(p=Plant.all),我知道我可以很容易地用类似 如果我做了两次连接,我将看到更多需要的记录和更长

试图找出一种有效的方法来选择跨多个表具有属性的记录。以下是基本设置:

结构

  • 植物(字段:id、名称、位置、颜色)(1000条记录)
  • 名称(字段:id、通用名称)(50条记录)
  • 位置(字段:id、床名)(125条记录)
型号

  • 植物-属于名称,属于位置
  • 名字-有很多植物吗
  • 位置-有很多工厂吗
我的目标是输出侧码中每一朵玫瑰的列表,并显示颜色,但我被select命令卡住了。如果我得到所有植物(
p=Plant.all
),我知道我可以很容易地用类似
如果我做了两次连接,我将看到更多需要的记录和更长的搜索时间。举个例子——我在16个不同的床上放了67朵玫瑰,然而,我在侧院里只有3朵

我的直觉告诉我,我应该能够做如下事情: 选择名为Rose的所有植物,然后从此选择中选择侧庭院中的所有玫瑰


有人能帮我指出正确的方向吗?

您可以将所有内容合并到一个查询中,如下所示:

Plant.joins(:name, :location).where(names: { common_name: "rose" }, locations: { bed_name: "side" })
SELECT "plants".* FROM "plants" INNER JOIN "names" ON "names"."id" = "plants"."name_id" INNER JOIN "locations" ON "locations"."id" = "plants"."location_id" WHERE "names"."common_name" = 'rose' AND "locations"."bed_name" = 'side'
这将导致一个SQL查询,如下所示:

Plant.joins(:name, :location).where(names: { common_name: "rose" }, locations: { bed_name: "side" })
SELECT "plants".* FROM "plants" INNER JOIN "names" ON "names"."id" = "plants"."name_id" INNER JOIN "locations" ON "locations"."id" = "plants"."location_id" WHERE "names"."common_name" = 'rose' AND "locations"."bed_name" = 'side'
注意,您必须在
where
子句中使用复数表名,但在
joins
子句中使用单数关联名

假设您的表被正确地索引,那么即使有大量的表,它也会几乎在瞬间运行

这是一个简单的示例,但可以使用条件进行相当复杂的联接。有关详细信息,请参阅

编辑

根据@Dan的评论,您可以通过使用
includes
预取连接中的关联数据来加快速度:

Plant.includes(:name, :location).where(names: { common_name: "rose" }, locations: { bed_name: "side" })
这将同时从
名称
位置
加载相关记录<代码>包含便于消除(或至少减少)N+1查询。它还足够聪明,知道何时可以在一个查询中检索所有数据,并在更有意义时返回到多个查询;您不必考虑它(尽管有时它会降低效率,所以如果您认为它会降低性能,请关注日志)

在这种情况下,使用
includes
是非常有效的,可以生成一个包含关联数据的SQL查询:

 SELECT "plants"."id" AS t0_r0, "plants"."color" AS t0_r1, "plants"."name_id" AS t0_r2, "plants"."location_id" AS t0_r3, "plants"."created_at" AS t0_r4, "plants"."updated_at" AS t0_r5, "names"."id" AS t1_r0, "names"."common_name" AS t1_r1, "names"."created_at" AS t1_r2, "names"."updated_at" AS t1_r3, "locations"."id" AS t2_r0, "locations"."bed_name" AS t2_r1, "locations"."created_at" AS t2_r2, "locations"."updated_at" AS t2_r3 FROM "plants" LEFT OUTER JOIN "names" ON "names"."id" = "plants"."name_id" LEFT OUTER JOIN "locations" ON "locations"."id" = "plants"."location_id" WHERE "names"."common_name" = 'rose' AND "locations"."bed_name" = 'side'

不确定我怎么会错过嵌套语法,但它正是我所需要的!我的查询已从6000ms变为60ms!:)您可以考虑添加<代码>。(包括:名称:位置)< /C> >以进一步提高性能。下面是关于这个主题的进一步阅读:谢谢@Dan,这样更好。我想我可以通过只查询必填字段来进一步优化它,但我现在把它作为一个练习留给读者。@Jim感谢更新代码,包含内容是有帮助的,尽管有点与我的方式(mis-)相反理解包括。作为旁注,我发现了一个gem调用
bullet[link](https://github.com/flyerhzm/bullet)
通知N+1问题之一并建议使用includes。它还告诉您是否有未使用的包含。