【问题标题】:Rails Not query on entire Where clauseRails 不查询整个 Where 子句
【发布时间】:2018-05-26 10:07:55
【问题描述】:

是否有一种直接的方法可以使用 ActiveRecord/ARel 否定整个 where 表达式?似乎where.not(),当给定一个参数散列时,单独否定每个表达式,而不是用单个 SQL NOT 否定整个事物。

Rails 示例

Thing.where.not(attribute1: [1,2], attribute2: [3,4], attribute3: [5,6])

会产生 SQL:

select * from things where attribute1 NOT IN (1,2) AND attribute2 NOT IN (3,4) AND attribute3 NOT IN (5,6)

这不是我想要做的。我想用一个 NOT 来否定整个 where 子句。

select * from things where NOT(attribute1 IN (1,2) AND attribute2 IN (3,4) AND attribute3 IN (5,6))

在布尔表示法中,Rails 似乎倾向于像这样否定 WHERE 子句的每个组件:

!(a) && !(b) && !(c)

但我想否定整个表达式:

! [ (a) && (b) && (c) ]

使用德摩根定律,我可以将我的查询写为!a || !b || !c,但这会导致一些相当长且丑陋的代码(Rails 5 和or 不那么长,但仍然丑陋)。我希望使用 ActiveRecord 或 Arel 时缺少一些语法糖?

背景故事

我正在编写 Ransack Equality Scope(例如 _eq)来搜索条件及其相反的条件。

scope :can_find_things_eq, ->(boolean = true) {
  case boolean
    when true, 'true'
      where(conditions_for_things)
    when false, 'false'
      where.not(conditions_for_things)
    end
  }

def self.ransackable_scopes(_auth_object = nil)
  %i[can_find_things_eq]
end

如果我使用上面的 Rails 5 建议和我开始使用的示例,我可以让我的否定查询工作......但是代码又长又丑。

where.not(attribute1: [1,2]).or(where.not(attribute2:
  [3,4)).or(where.not(attribute3: [5,6))

链式 Ors 和 WhereNots 有效,但可读性不强。除了必须使用德摩根定律手动/逻辑地否定它之外,有没有更好的方法来否定这个where

谢谢!

【问题讨论】:

  • 你试过 Thing.where.not(Thing.where(attribute1: [1,2], attribute2: [3,4], attribute3: [5,6])) 吗?
  • 否,因为您的内部查询生成 AND 节点,而不是 OR 节点。请参考我的第一个例子。
  • 偶然发现了这个 SO。不确定以下是否会有所帮助,因为我不知道除 SQL 之外的任何技术。但是,如果通过对布尔值进行算术运算可以强制布尔值表现为 1(真)和 0(假),那么如果任何一个为假,则将它们全部相乘将意味着乘以零,因此产生零。所以![(a) && (b) && (c)] 将等同于测试(a)*(b)*(c) == 0

标签: sql ruby-on-rails activerecord arel


【解决方案1】:

如果您使用的是 Rails 7,则可以使用 invert_where。这将完全按照您的需要形成查询

! [ (a) && (b) && (c) ]

【讨论】:

  • 很好的建议。 Rails 7 仍然是 alpha,但我一定会记住这一点!
  • 很高兴您发现这很有帮助。如果您想了解更多信息,Here 是一个很棒的博客。谢谢!
【解决方案2】:

您可以使用 Arel 产生所需的结果,如下所示:

table = Thing.arel_table
clause = Arel::Nodes::Not.new(
  table[:attribute1].in([1,2]).and(
    table[:attribute2].in([2,3]).and(
      table[:attribute3].in([5,6])
    )))
Thing.where(clause)

或者

Thing.where.not(
  table[:attribute1].in([1,2]).and(
    table[:attribute2].in([2,3]).and(
      table[:attribute3].in([5,6])
    )))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多