多对多自我联接表上的ActiveRecord查询

时间:2018-10-27 20:26:11

标签: sql ruby-on-rails activerecord

我有一个名为people的多对多自联接表,它使用以下模型:

class Person < ApplicationRecord
  has_and_belongs_to_many :children,
    class_name: "Person",
    join_table: "children_parents",
    foreign_key: "parent_id",
    association_foreign_key: "child_id",
    optional: true

  has_and_belongs_to_many :parents,
    class_name: "Person",
    join_table: "children_parents",
    foreign_key: "child_id",
    association_foreign_key: "parent_id",
    optional: true
end

如果在上述模型中看不到-除了数据库中的people表之外,还有一个children_parents联接表,其中包含两个外键索引字段child_idparent_id。这使我们能够表示孩子与父母之间的多对多关系。

我想查询一个人的兄弟姐妹,所以我向Person模型添加了以下方法:

def siblings
  self.parents.map do |parent|
    parent.children.reject { |child| child.id == self.id }
  end.flatten.uniq
end

但是,这将进行三个SQL查询:

  Person Load (1.0ms)  SELECT "people".* FROM "people" INNER JOIN "children_parents" ON "people"."id" = "children_parents"."parent_id" WHERE "children_parents"."child_id" = $1  [["child_id", 3]]
  Person Load (0.4ms)  SELECT "people".* FROM "people" INNER JOIN "children_parents" ON "people"."id" = "children_parents"."child_id" WHERE "children_parents"."parent_id" = $1  [["parent_id", 1]]
  Person Load (0.4ms)  SELECT "people".* FROM "people" INNER JOIN "children_parents" ON "people"."id" = "children_parents"."child_id" WHERE "children_parents"."parent_id" = $1  [["parent_id", 2]]

我知道可以像这样使单个SQL查询成为可能:

SELECT DISTINCT(p.*) FROM people p
INNER JOIN children_parents cp ON p.id = cp.child_id
WHERE cp.parent_id IN ($1, $2)
AND cp.child_id != $3

$1$2是此人的父母ID,而$3是该人的ID。

是否可以使用ActiveRecord进行此查询?

1 个答案:

答案 0 :(得分:0)

您可以使用以下内容:

def siblings
  Person.select('siblings.*').from('people AS siblings').where.not(id: id)
    .where(
      parents.joins(
        'JOIN children_parents ON parent_id = people.id AND child_id = siblings.id'
      ).exists
    )
end

在这里您可以看到一些奇怪的东西:

开始设置表格别名。而且您应该避免这种情况,因为在此类表别名之后,活动记录将不再对ruby中的列名有所帮助:where(column:value).order(:column)-将不起作用,仅保留纯SQL字符串

存在-我经常使用它而不是联接。当您将许多记录合并为一条记录时,您会收到重复记录,然后出现与众不同 group 及其相关的新问题。 Exists 还提供了查询隔离:EXISTS表达式中的表和列对于查询的其他部分不可见。在Rails中使用它的不好之处:至少需要1个普通SQL。

此方法的一个缺点:如果要在某处为每个记录调用它,那么每个记录将有1个查询-N + 1问题。

现在关于The Rails Way的几句话。 Rails指南建议始终使用has_many:through而不是habtm,我在这里看到了它:https://github.com/rubocop-hq/rails-style-guide

据我所知,Rails意识形态代表发展速度和维护简便性。首先是说性能无关紧要(想像一下您需要启动多少个用户会遇到问题),其次则说普通SQL的灵活性很好,但是在rails中没有问题,在rails中请使代码尽可能简单(请参阅rubocop默认值:方法中有10个位置,类中有100个位置,以及4个复杂度指标(总是表示您的代码过于复杂)。我的意思是,许多现实世界的Rails项目都使用N + 1进行查询,进行无效查询,这很少成为问题

因此,在这种情况下,我建议尝试包含预加载急切加载