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

Sql 多对多自联接表上的ActiveRecord查询,sql,ruby-on-rails,activerecord,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:

我有一个名为
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
但是,这会产生三个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进行此查询?

您可以使用以下方法:

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
在这里你可以看到一些奇怪的东西:

设置表格别名。您应该避免这种情况,因为在这样的表别名之后,activerecord对于ruby:where(column:value)中的列名将不再有任何帮助。order(:column)-将不起作用,只剩下普通的sql字符串

存在-我经常使用它而不是连接。当您将多个记录合并到一个记录时,您会收到重复的记录,然后出现distinctgroup以及新的问题Exists还提供了查询隔离:Exists表达式中的表和列对于查询的其他部分是不可见的。在rails中使用它的缺点是:至少需要1个普通SQL

这种方法的一个缺点是:如果您将在某个地方为每个记录调用它,那么您将为每个记录有一个查询—N+1问题

现在,关于铁路的几句话。Rails指南建议始终使用has_许多:通过而不是habtm,我在这里看到了:

据我所知,Rails代表着开发的速度和维护的简单性。第一个意思是性能不重要(想象一下,您需要多少用户开始使用它),第二个意思是普通SQL的灵活性很好,但在rails中不是这样,在rails中,请使代码尽可能简单(参见rubocop默认值:方法中有10个loc,类中有100个loc,4个复杂度指标总是说您的代码太复杂了)。我的意思是,许多真实世界的rails项目都使用N+1进行查询,导致查询无效,这很少成为问题

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

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