【问题标题】:Getting all results from `includes` while filtering the parent model based on included elements?根据包含的元素过滤父模型时从“包含”中获取所有结果?
【发布时间】:2013-06-10 22:01:35
【问题描述】:

我正在处理我们应用程序中的一个小错误,我将在此处尝试对其进行简化。

我有这个公司模型(实际模型有更多的关联,但这里显示了问题):

class Company < ActiveRecord::Base
  has_many :contacts

  def self.search(query, page=1)
    companies = includes(:contacts).order("companies.name ASC")

    if query.present?
      companies = companies.where(%(
        companies.name ILIKE :q,
        OR contacts.name ILIKE :q
      ), q: "%#{query}%")
    end

    companies.paginate(page: page)
  end
end

现在这主要是工作得很好。唯一的问题是当一家公司有 2 个或更多联系人时,用户会根据其中一个联系人姓名进行搜索。正确找到了公司,我们的自动完成 UI 显示了公司。然而,当用户从自动完成中选择公司时,他们应该能够从下拉列表中选择一个联系人,但下拉列表最终只包含他们过滤到的一个联系人。公司应根据其搜索条件进行过滤,但该下拉列表仍应包含公司的所有联系人供用户选择。

我知道为什么这样做,这是因为 Rails 正在这样做,它会在查询运行时自动加载所有关联,以便它可以同时获取所有关联属性 和 同时包含所有条件。

它正在运行select {every_attribute_from_every_included_table} left outer join {all_included_tables} 类型的查询,但我想要它做的是:

select companies.* from companies
left outer join contacts <and other included tables> on <join criteria>
where <filter criteria>

过滤后跟a的公司:

select contacts.* from contacts
where company_id IN (<company_ids from previous query>)

如果include 表也没有包含在where 子句中,它通常会如何包含条目。

Rails 在这里做了太多的魔法,我希望它恢复正常的魔法,而不是特殊的魔法!我怎样才能做到这一点?或者有什么更好的方法来完成我想要完成的事情(仍然根据联系人姓名进行过滤,但在我拨打company.contacts 时不要阻止其他联系人被包括在内)。

【问题讨论】:

    标签: sql ruby-on-rails postgresql ruby-on-rails-3.2


    【解决方案1】:

    好的,我们随意查询

    select companies.* from companies
    left outer join contacts <and other included tables> on <join criteria>
    where <filter criteria>
    

    让我们手动覆盖.joins(...)

    Company.includes(:contacts).
            joins("left outer join contacts c on c.company_id = companies.id").
            where(...)
    

    这将使:contacts 关系保持不变,而是加入同一个contacts 表的单独“别名”。

    【讨论】:

    • 这似乎按预期工作 :) 我明天会再检查一下,以防它有意外的副作用,但我认为这就是我想要的。谢谢!
    【解决方案2】:

    我发现您需要两个查询:

    • 一个选择公司(或多个公司)
    • 用于选择(选定)公司的联系人

    使用 Rails 魔法,您可以使用 joins 选择公司:

    @companies = Company.joins(:contacts).where(...).uniq
    

    SELECT DISTINCT "companies".* FROM "companies" INNER JOIN "contacts" ON ... WHERE (...)

    然后获取特定公司的所有联系人:

    company= @companies.first
    @contacts = company.contacts
    

    【讨论】:

    • 在@Deefour 的回答中查看 cmets
    • 看起来他删除了那个答案,所以那些 cmets 不见了。这里的问题是joins(:contacts) 进行内部联接,不会选择那些没有任何联系人的公司(不良行为)。期望的行为是它对联系人进行左外连接,过滤联系人姓名等,但仍将所有联系人结果包含在最终查询中。它还需要像includes(:contacts) 那样自动加载这些联系人,这样company.contacts 行就不会进行n+1 查询。
    猜你喜欢
    • 1970-01-01
    • 2021-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多