【问题标题】:Rails - how to combine incompatible statements with OR (Rails 5)Rails - 如何将不兼容的语句与 OR (Rails 5) 结合使用
【发布时间】:2020-02-19 14:18:46
【问题描述】:

有项目模型和支付模型:

class Project < ApplicationRecord
  has_many :payments
end

class Payment < ApplicationRecord
  belongs_to :project
end

对我来说,将列查询与 orand 组合起来非常容易。

Project.where(projects[:created_at].gt(6.months.ago).or(projects[:updated_at].gt(1.month.ago)))

使用 Arel 的原因是确切的列、运算符和值来自用户,并且 Arel 运算符与用户可以选择的内容非常匹配。

但是,如果我想将它与另一个关联表中的数据结合起来,那么我就是不知道该怎么做。 ActiverRecord 要求OR 在结构上兼容,所以我做不到:

Project.where(projects[:created_at].gt(6.months.ago)).or(Project.joins(:payments))

类似这样的: Project.where(projects[:created_at].gt(6.months.ago).or(projects.join(payments).on(projects[:id].eq(payments[:project_id]))))

引发异常:ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: subquery must return only one column.

我能够做到的一种方法是: Project.where(projects[:created_at].gt(6.months.ago)).or(Project.where(id: Project.joins(:payments).select(:id)))

哪个生成了 SQL SELECT "projects".* FROM "projects" WHERE (("projects"."created_at" &gt; '2019-08-19 09:11:22.064298') OR "projects"."id" IN (SELECT "projects"."id" FROM "projects" INNER JOIN "payments" ON "payments"."project_id" = "projects"."id"))

这是最好的方法还是有更好的方法?请可能有 10 多个这样的子句组合在一起,涉及多个表,在所有可能的表上使用 include 以使所有单独的子句兼容可能太昂贵了。

【问题讨论】:

    标签: ruby-on-rails arel


    【解决方案1】:

    您是否有任何理由无法在不同条件下保持联接一致?例如在 Rails 5+ 中你可以这样写:

    Project.where(some_field: '12').joins(:payments)
           .or(
               Project.where(payments: {id: '1'}).joins(:payments)
           )
    

    这会生成如下 SQL:

    SELECT projects.* FROM projects
    INNER JOIN payments ON (payments.project_id = project.id)
    WHERE projects.some_field = 12 OR payments.id = 1
    

    注意:

    使用 OP 中结构不兼容的示例,您可以简单地在第一个条件中添加一个连接以使其兼容:

    Project.where(projects[:created_at].gt(6.months.ago)).joins(:payments).or(Project.joins(:payments))
    

    【讨论】:

    • 我已经考虑过了,但正如我在问题中提到的,我正在组合多个这样的子句(或条件,如果你愿意的话)。因此,我最终可能需要将 4 个以上的表加入到原始的 projects 表中。你会说在这种情况下,我最好像你说的那样加入表并运行查询吗?或者我提到的方式即Project.where(projects[:created_at].gt(6.months.ago)).or(Project.where(id: Project.joins(:payments).select(:id)))我对SQL不是很熟悉,但是我尝试过几次,4是性能受到很大影响的连接数。
    • @arunt 为了简单起见,我建议在子查询上使用连接并利用列上的索引。话虽如此,没有完美的答案。在某些情况下,每种方法都可能是最快的方法。
    • 谢谢。我已接受您的回答,因为这似乎是一个不错的选择。我想没有单一的解决方案可以获得最佳性能。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-18
    • 2021-01-22
    • 2021-10-24
    • 2017-05-02
    • 2020-11-28
    • 2014-02-20
    相关资源
    最近更新 更多