如何在自引用 has_and_belongs_to_many 中查找没有父项的条目?

发布于 2024-12-19 23:01:06 字数 881 浏览 2 评论 0原文

两个模型,第一个是自引用的:

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 不太熟悉,所以也许有人很快就知道如何实现这一点,而不是使用遍历所有页面的蛮力方法?

Two models with the first being self-referential:

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

Which means I can easily find both parents and children through @page.parents and @page.children.
Now, here's the question: How do I find the "orphans" (or trunks, if you want to go tree-style, i.e. without parents) and the dead-ends (or leafs, i.e. without children) on a global basis?
I'm not that firm in SQL, so maybe someone has a fast idea how to accomplish that instead of a brute-force approach which iterates over all the pages?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

爱你是孤单的心事 2024-12-26 23:01:06

编辑

等一下;实际上,有一个解决方案:使用左外连接。

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

例如,这将找到所有主干页面(仅将所有页面与其“父”关联连接起来,并选择没有关联的页面)。

不过,我不知道它会对性能产生什么影响;我认为它应该相当而不是太慢,但不应该用作常见任务。也许使用 Arel Table 会更简单(但遗憾的是我对这些还不够熟悉)。


我不知道除了暴力迭代之外是否还有其他方法(如果你找到了,我想知道)。

我的建议是将这些属性捕获为页面的布尔属性,并使用回调将它们维持在一致的状态。

这个想法是在 Page 模型上添加 before_saveafter_savebefore_destroy... 回调,以便每个当我们操作一个页面时,我们检查它是“树干”还是“叶子”(通过通过 parents.exist?children.exists?< 检查它是否有父母或孩子) /代码>) ;然后我们修改此页面上的布尔 trunkleaf 属性(或者在销毁的情况下在关联页面上)。

这会以某种方式降低插入/更新/删除的性能,但允许使用简单的 where( trunk: true ) 语句快速获取树干和叶子。您可能必须在 Page 模型上使用 default_scope include( :parents, :children ) (或者至少经常使用 includes )来防止DB命中数爆炸。

也许可以使用相同的策略,但将回调放在 PageRelation 模型上;甚至可以使用观察者。这完全取决于您的具体需求和编码风格,并且在这里开发的时间会很长。

edit

wait a minute ; there is, actually, a solution : use a left outer join.

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

this, for instance, will find all trunk pages (just joins all pages with their "parent" associations, and select pages with no association).

i don't know what impact it will have on performance, though ; i think it should be reasonably fast not too slow, but not to use as a common task. Maybe it would be simpler using an Arel Table (but i'm not familiar enough with these, sadly).


i don't know if there is any other way than brute-force iteration here (and if you find one, i'd like to know it).

My advice would be to capture these properties as boolean attributes of the pages and use callbacks to maintain these in a coherent state.

The idea is to add before_save, after_save, before_destroy... callbacks on the Page Model, so that every time we manipulate a page, we check if it is a "trunk" or a "leaf" (by checking if it has parents or children via parents.exist? and children.exists?) ; then we modify boolean trunk and leaf attributes on this page ( or on the associated page in case of a destroy ).

This will somehow slow down performance on insert / update / delete, but allow to fetch trunks and leaves really fast with a simple where( trunk: true ) statement. You will probably have to use default_scope includes( :parents, :children ) on the Page model (or at least use includes a lot) to prevent the number of DB hits to explode.

Maybe it's possible to use the same strategy, but to place the callbacks on the PageRelation model ; it may even be possible to use an Observer. It all depends on your specific needs and coding style, and would be far to long to develop here.

楠木可依 2024-12-26 23:01:06

找到叶子:

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

Find the leaves:

select * from pages where id not in (select parent_id from page_relations)
素衣风尘叹 2024-12-26 23:01:06

NOT EXISTS 正是您想要的(并且比正确的连接更不难看,并且不会遇到像 IN 这样的 NULL 问题)

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

更新:在 @Rhywden 的评论之后更改了 WHERE 条件。

NOT EXISTS does exactly what you want (and is less ugly than the right join and does not suffer from the NULL problem like IN) :

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

Update: changed the WHERE condition after @Rhywden's comment.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文