Запрос ActiveRecord для таблицы самоотключения «многие ко многим» - PullRequest
0 голосов
/ 27 октября 2018

У меня есть таблица самообъединения «многие ко многим», которая называется 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_id и parent_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 - это родительские идентификаторы человека, а $3 - это идентификатор человека.

Есть ли способ сделать этот запрос с помощью ActiveRecord?

1 Ответ

0 голосов
/ 28 октября 2018

Вы можете использовать что-то вроде этого:

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

существует - я использую его очень часто вместо соединений.Когда вы объединяете множество записей в одну, вы получаете дубликаты, затем приходит отчетливая или группа и новые проблемы с ними. Exists также обеспечивает изоляцию запроса: таблица и столбцы в выражении EXISTS невидимы для других частей запроса.Плохая часть использования его в рельсах: необходим как минимум 1 простой SQL.

Одна слабость этого метода: если вы будете вызывать его для каждой записи где-то, то у вас будет 1 запрос для каждой записи - N +1 проблема.

Теперь несколько слов о Rails Way.Руководство по Rails предлагает всегда использовать has_many: через вместо habtm я видел это здесь: https://github.com/rubocop-hq/rails-style-guide.

Идеология Rails, как я понял, означает скорость разработки и простоту обслуживания.Во-первых, это означает, что производительность не имеет значения (просто представьте, сколько пользователей нужно запустить, чтобы иметь проблемы с ней), во-вторых, говорит, что гибкость простого SQL хороша, но не в рельсах, в рельсах сделайте код максимально простым (см. Rubocopзначения по умолчанию: 10 loc в методе, 100 loc в классе и 4 метрики сложности, всегда говорящих о том, что ваш код слишком сложный).Я имею в виду, что многие реальные проекты rails делают запросы с N + 1, делают неэффективные запросы, и это редко становится проблемой

Так что в таких случаях я бы рекомендовал попробовать включает , предварительная загрузка , eager_load .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...