Как найти записи без родителей в самореферентных has_and_belongs_to_many? - PullRequest
1 голос
/ 04 декабря 2011

Две модели, первая из которых ссылается на себя:

def Page < ActiveRecord::Base
  has_many :source_page_relations,
           :class_name => 'PageRelation',
           :foreign_key => :child_id,
           :dependent => :destroy
  has_many :child_page_relations,
           :class_name => 'PageRelation',
           :foreign_key => :parent_id,
           :dependent => :destroy
  has_many :children, :through => :child_page_relations
  has_many :parents, :through => :source_page_relations
end

def PageRelation < ActiveRecord::Base
  belongs_to :parent, :class_name => 'Page', :foreign_key => :parent_id
  belongs_to :child,  :class_name => 'Page', :foreign_key => :child_id
end

Это означает, что я легко могу найти родителей и детей через @ page.parents и @ page.children. Теперь вот вопрос: как мне найти «сирот» (или сундуки, если вы хотите использовать стиль дерева, то есть без родителей) и тупики (или листы, то есть без детей) в глобальном масштабе? Я не настолько сильна в SQL, так что, может быть, у кого-то есть быстрая идея, как это сделать, вместо того, чтобы использовать метод грубой силы, который перебирает все страницы?

Ответы [ 3 ]

2 голосов
/ 04 декабря 2011

изменить

подождите минуту;на самом деле есть решение: использовать левое внешнее объединение.

Page
  .joins("LEFT OUTER JOIN page_relations ON pages.id = page_relations.child_id")
  .where("page_relations.child_id IS NULL")

это, например, найдет все страницы соединительных линий (просто объединяет все страницы с их "родительскими" ассоциациями и выбирает страницы безассоциация).

я не знаю, какое влияние это окажет на производительность;я думаю, что это должно быть разумно быстро не слишком медленно, но не для использования в качестве обычной задачи.Возможно, было бы проще использовать таблицу Ареля (но, к сожалению, я недостаточно знаком с ними). ​​


Я не знаю, существует ли здесь какой-либо другой способ, кроме итерации методом грубой силы (и если вы найдете один, я бы хотел это знать).

Мой совет - записывать эти свойства в качестве логических атрибутов страниц и использовать обратные вызовы для поддержания их в согласованном состоянии.

Идея состоит в том, чтобы добавить before_save, after_save, before_destroy ... обратных вызовов на модель Page, чтобы каждый раз, когда мы манипулировали страницей, мы проверяли, является ли она "стволом" или "лист "(проверяя, есть ли у него родители или дети через parents.exist? и children.exists?);затем мы изменяем логические атрибуты trunk и leaf на этой странице (или на соответствующей странице в случае уничтожения).

Это каким-то образом замедлит производительность при вставке / обновлении / удалении, но позволит очень быстро извлекать стволы и листья с помощью простого оператора where( trunk: true ).Вам, вероятно, придется использовать default_scope includes( :parents, :children ) на модели Page (или, по крайней мере, использовать includes много), чтобы предотвратить взрыв количества попаданий в БД.

Возможно, возможно использовать ту же стратегию, но для размещения обратных вызовов на модели PageRelation;может даже быть возможно использовать Observer.Все зависит от ваших конкретных потребностей и стиля кодирования, и его развитие будет очень долгим.

1 голос
/ 04 декабря 2011

NOT EXISTS делает именно то, что вы хотите (и менее уродливо, чем правильное соединение, и не страдает от проблемы NULL, такой как IN) :

SELECT *
  FROM pages pp
  WHERE NOT EXISTS ( SELECT *
      FROM page_relations pr
      WHERE pr.child_id = pp.id -- Changed
      )
  ;

Обновление: изменено условие WHERE после комментария @ Rhywden.

1 голос
/ 04 декабря 2011

Найди листья:

select * from pages where id not in (select parent_id from page_relations)
...