【问题标题】:Rails associations NOT EXISTS. Better way? [duplicate]Rails 关联不存在。更好的方法?
【发布时间】:2012-11-09 21:18:40
【问题描述】:

使用 Rails 3.2.9
我正在尝试获取与没有所有者的组织相关联的项目列表。

我能够使用下面的方法获得一个数组列表,但对我来说似乎很难看。有没有更好的方法来做到这一点?

Items.all(:select => "items.id, items.name",
  :joins => "INNER JOIN organizations on items.organization_id = organizations.id",
  :conditions => "NOT EXISTS (select * from items k JOIN items_owners on items.id = items_owners.item_id) and items.organization_id = 1")

表设置:
业主:

  • 身份证
  • 姓名

项目:

  • 身份证
  • 姓名
  • organization_id

items_owners:

  • owner_id
  • item_id

组织:

  • 身份证
  • 列表项

型号:

class Organization < ActiveRecord::Base
   attr_accessible :name

   has_many :items
end

class Item < ActiveRecord::Base
   attr_accessible :description, :name, :owner_ids, :organization_id

   has_many :items_owner
   has_many :owners, :through => :items_owner
   belongs_to :organization
end

class Owner < ActiveRecord::Base
   attr_accessible :name

   has_many :items_owner
   has_many :items, :through => :items_owner
end

class ItemsOwner < ActiveRecord::Base
   attr_accessible :owner_id, :item_id

   belongs_to :item
   belongs_to :owner
end

【问题讨论】:

  • 我认为一个Item应该belongs_to :organization,而Organization has_many :items,会更有意义
  • Corrected 与另一个项目混淆了。

标签: ruby-on-rails sqlite ruby-on-rails-3.2


【解决方案1】:

编辑:删除 .all,添加 references 并添加使用 arel_table

Items.joins(:organization).includes(:owners).references(:owners).
  where('owners.id IS NULL')

如果你想同时使用includes

Items.includes(:organization, :owners).references(:organization, :owners).
  where('organisations.id IS NOT NULL AND owners.id IS NULL')

正如@Dario Barrionuevo 所写,它应该是belongs_to :organisation in Item

在第一个示例中使用arel_table

Items.joins(:organization).includes(:owners).references(:owners).
  where(Owner.arel_table[:id].eq(nil))

在 Rails 5 中(来自@aNoble 的评论):

Items.joins(:organization).left_joins(:owners).
  where(Owner.arel_table[:id].eq(nil))

但如果要在代码中引用关系,则使用includes 仍然是更可取的,以避免额外的读取。

【讨论】:

  • 这两种方法都有效,但我必须全部删除。 SQL 输出看起来很疯狂,但它确实有效。谢谢
  • 您的意思是在两个建议中都删除“.all”?在这种情况下,我最好改变我的建议。
  • 在 Rails 上的从不版本(我正在测试 5.2)中,您应该使用 left_joins 而不是 includes
  • 谢谢@aNoble,我已经更新了我的答案。但在某些情况下,最好使用包含(也许在 Rails 5 中有另一种方法?)。
【解决方案2】:

在 Rails 5、6 中有很多方法可以做到 NOT EXISTS:

  1. 不同的项目 OUTER JOIN item_owners 其中 item_owners.id 为空
  2. items.id 不在(从 item_owners 中选择 item_id)
  3. 不存在(从 item_owners 中选择 1 个,其中 item_id = items.id)
  4. where (select COUNT(*) from item_owners where item_id = items.id) = 0

我能想到 4 种方法,但我似乎记得有 7 种方法。无论如何,这是一个切线,但可能会给您一些更适合您的用例的想法。

我发现使用 NOT IN 方法对我的团队来说是最容易创建和维护的。 我们的目标是避免 arel,支持 owner 表中的 WHERE 子句(例如:admin owner),并支持多个级别的 rails :through。

Items.where.not(id: Items.joins(:owners).select(:id))
     .select(:id, :name)

Items.where.not(id: Items.joins(:items_owners).select(:id))
     .select(:id, :name)

Items.where.not(id: ItemOwners.select(:item_id))

我们使用第一个,但这些示例应该按照从最优化到最佳的顺序排列。同样按照对模型的了解最少到最多的顺序。

【讨论】:

    【解决方案3】:

    试试这个

    Items.joins(:organisations).where(Items.joins(:items_owners).exists.not).select('items.id,items.name')
    

    【讨论】:

    • 这很接近。下面是 SQL 输出。问题是最后一个连接的范围是使用当前选择中的 items.id 而不是顶部选择。 SELECT items.id,items.name FROM "items" INNER JOIN "organizations" ON "organizations"."id" = "items"."organization_id" WHERE (NOT (EXISTS (SELECT "items".* FROM "items" INNER加入 "items_owners" ON "items_owners"."item_id" = "items"."id")))
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多