什么是正确的使用方式包括(Rails 4,ActiveRecord,PostgreSQL)

时间:2015-10-28 11:14:28

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

我有以下型号:

月亮 - >行星 - >明星 - > Galaxy - >宇宙

月亮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等来电。
  3. 我尝试了两种方法。第一个(m1)在includes上调用Moon,所有关系都展平。这导致查询效率非常低,但我可以调用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"
    

    查询的差异

    Inefficient vs. Efficient

1 个答案:

答案 0 :(得分:0)

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

我认为您在这些不正确的关联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这样使用{{1}}