【问题标题】:What is the proper way to use includes (Rails 4, ActiveRecord, PostgreSQL)什么是正确的使用方法包括(Rails 4、ActiveRecord、PostgreSQL)
【发布时间】:2015-10-28 11:14:28
【问题描述】:

我有以下型号:

月球 -> 行星 -> 恒星 -> 银河 -> 宇宙

月亮belongs_to行星,行星belongs_to星星等等:


class Moon < ActiveRecord::Base
  belongs_to :planet, inverse_of: :moons

  has_one :star, through: :planet
  has_one :galaxy, through: :star
  has_one :universe, through: :galaxy
end

class Planet < ActiveRecord::Base
    belongs_to :star, inverse_of: :planets
    has_many :moons, inverse_of: :planet, dependent: :destroy
end

问题

我试图让 Rails 将这些对象的层次结构加载到内存中。我正在努力实现两件事:

  1. 使用一个高效的查询加载所有内容
  2. 无需额外调用数据库即可使用 moon.galaxy 而不是 moon.planet.star.galaxy 等调用。

我尝试了两种方法。第一个(m1)在Moon 上调用includes,所有关系都变平了。这导致查询效率非常低,但我可以调用m1.galaxy。第二个 (m2) 使用关系层次结构调用 includes。这会产生高效的查询,但我可以调用m2.galaxy 而无需访问数据库。

这样做的正确方法是什么?


示例

m1 - 低效查询,m2 - 高效查询

irb(main):152:0* m1 = Moon.includes(:planet, :star, :galaxy).where(galaxies: {name: 'The Milky Way'}).first
  SQL (1.9ms)  SELECT  "moons"."id" AS t0_r0, "moons"."name" AS t0_r1, "moons"."planet_id" AS t0_r2, "moons"."created_at" AS t0_r3, "moons"."updated_at" AS t0_r4, "planets"."id" AS t1_r0, "planets"."name" AS t1_r1, "planets"."star_id" AS t1_r2, "planets"."created_at" AS t1_r3, "planets"."updated_at" AS t1_r4, "stars"."id" AS t2_r0, "stars"."name" AS t2_r1, "stars"."galaxy_id" AS t2_r2, "stars"."created_at" AS t2_r3, "stars"."updated_at" AS t2_r4, "galaxies"."id" AS t3_r0, "galaxies"."name" AS t3_r1, "galaxies"."universe_id" AS t3_r2, "galaxies"."created_at" AS t3_r3, "galaxies"."updated_at" AS t3_r4 FROM "moons" LEFT OUTER JOIN "planets" ON "planets"."id" = "moons"."planet_id" LEFT OUTER JOIN "planets" "planets_moons_join" ON "planets_moons_join"."id" = "moons"."planet_id" LEFT OUTER JOIN "stars" ON "stars"."id" = "planets_moons_join"."star_id" LEFT OUTER JOIN "planets" "planets_moons_join_2" ON "planets_moons_join_2"."id" = "moons"."planet_id" LEFT OUTER JOIN "stars" "stars_moons_join" ON "stars_moons_join"."id" = "planets_moons_join_2"."star_id" LEFT OUTER JOIN "galaxies" ON "galaxies"."id" = "stars_moons_join"."galaxy_id" WHERE "galaxies"."name" = $1  ORDER BY "moons"."id" ASC LIMIT 1  [["name", "The Milky Way"]]
=> #<Moon id: 1, name: "The Moon", planet_id: 1, created_at: "2015-10-28 10:02:04", updated_at: "2015-10-28 10:02:04">

irb(main):153:0> m2 = Moon.includes(planet: {star: :galaxy}).where(galaxies: {name: 'The Milky Way'}).first
  SQL (0.5ms)  SELECT  "moons"."id" AS t0_r0, "moons"."name" AS t0_r1, "moons"."planet_id" AS t0_r2, "moons"."created_at" AS t0_r3, "moons"."updated_at" AS t0_r4, "planets"."id" AS t1_r0, "planets"."name" AS t1_r1, "planets"."star_id" AS t1_r2, "planets"."created_at" AS t1_r3, "planets"."updated_at" AS t1_r4, "stars"."id" AS t2_r0, "stars"."name" AS t2_r1, "stars"."galaxy_id" AS t2_r2, "stars"."created_at" AS t2_r3, "stars"."updated_at" AS t2_r4, "galaxies"."id" AS t3_r0, "galaxies"."name" AS t3_r1, "galaxies"."universe_id" AS t3_r2, "galaxies"."created_at" AS t3_r3, "galaxies"."updated_at" AS t3_r4 FROM "moons" LEFT OUTER JOIN "planets" ON "planets"."id" = "moons"."planet_id" LEFT OUTER JOIN "stars" ON "stars"."id" = "planets"."star_id" LEFT OUTER JOIN "galaxies" ON "galaxies"."id" = "stars"."galaxy_id" WHERE "galaxies"."name" = $1  ORDER BY "moons"."id" ASC LIMIT 1  [["name", "The Milky Way"]]
=> #<Moon id: 1, name: "The Moon", planet_id: 1, created_at: "2015-10-28 10:02:04", updated_at: "2015-10-28 10:02:04">

m1 - 我可以在内存中调用moon.galaxy.name,m2 - moon.galaxy.name 调用DB

irb(main):154:0> m1.galaxy.name
=> "The Milky Way"

irb(main):155:0> m2.galaxy.name
  Galaxy Load (0.5ms)  SELECT  "galaxies".* FROM "galaxies" INNER JOIN "stars" ON "galaxies"."id" = "stars"."galaxy_id" INNER JOIN "planets" ON "stars"."id" = "planets"."star_id" WHERE "planets"."id" = $1 LIMIT 1  [["id", 1]]
=> "The Milky Way"

m1 - moon.planet.star.galaxy.name 调用 DB,m2 - 我可以在内存中调用 moon.planet.star.galaxy.name

irb(main):156:0> m1.planet.star.galaxy.name
  Star Load (3.3ms)  SELECT  "stars".* FROM "stars" WHERE "stars"."id" = $1 LIMIT 1  [["id", 1]]
  Galaxy Load (0.3ms)  SELECT  "galaxies".* FROM "galaxies" WHERE "galaxies"."id" = $1 LIMIT 1  [["id", 1]]
=> "The Milky Way"

irb(main):157:0> m2.planet.star.galaxy.name
=> "The Milky Way"

查询的差异

【问题讨论】:

  • 关于急切加载,请考虑阅读在 Rails 中执行此操作的不同方法。有许多文章(有些比其他文章更新)详细,您应该能够在那里找到一些信息以提供帮助,例如,blog.bigbinary.com/2013/07/01/…

标签: sql ruby-on-rails postgresql ruby-on-rails-4 rails-activerecord


【解决方案1】:

嗯,我认为您的第二种方式(m2)是正确的。 Bay 还可以考虑 eager_load,因为 includes 可能会触发多个 SQL 查询,而不是为每个关联(planet、start 等)触发一个查询。

我认为您的问题在于这些不正确的关联 stargalaxyuniverse

class Moon < ActiveRecord::Base
  belongs_to :planet, inverse_of: :moons

  has_one :star, through: :planet
  has_one :galaxy, through: :star
  has_one :universe, through: :galaxy
end

只需删除它们,然后重试。

请注意您不能以这种方式使用has_one (example in documentation)

【讨论】:

  • 感谢您的回答。我不相信将has_one through:belongs_to 一起使用是不正确的。见这里:apidock.com/rails/ActiveRecord/Associations/ClassMethods/… 另外,如果我删除它们,那么我将无法写moon.galaxy,这是我最初尝试做的一部分。
  • 1) 在 Moon 模型中使用 has_one 关联不正确。您可以找到以下语句“仅当其他类包含外键时才应使用此方法。”当点击你的链接时:)。
  • 2) 如果您想从moon 访问galaxy(例如)是通过belongs_to 关联直接访问一个选项吗?我的意思是跟随:在月球模型delegate :star, to: :planet, prefix: false; delegate :galaxy, :to :star, prefix: false.
  • 1)来自文档:“您只能通过连接模型上的 has_one 或 belongs_to 关联使用 :through 查询”2)好点。知道当关系是 has_many 时我如何做到这一点(例如,我希望能够编写galaxy.moons)
  • 1) 仅供参考,检查 belongs_to 关联 (apidock.com/rails/ActiveRecord/Associations/ClassMethods/…) 的文档,:through 未列出。同样,您不能在 Moon 模型中使用 has_one 关联。 2) 从父模型(行星、星系等)访问子模型(卫星)很容易 - 只需在行星和星系模型中使用 has_many and has_many :through
猜你喜欢
  • 2018-09-05
  • 2012-01-19
  • 1970-01-01
  • 2013-04-04
  • 1970-01-01
  • 1970-01-01
  • 2012-08-22
  • 1970-01-01
  • 2020-09-29
相关资源
最近更新 更多