将Arel分组为AND和OR子句

时间:2013-02-23 06:42:46

标签: ruby-on-rails activerecord arel

我正在尝试在我的rails模型中构建一个范围,在调用时,它会在7个布尔字段上给我一组嵌套的AND和OR子句。为清晰起见,这是一个简化列名的示例:

SELECT * FROM mytable
WHERE (a AND b AND c) OR (a AND d AND e) OR (a AND f AND g);

请注意,列a出现在所有三个子句中。另一种写作方式是:

SELECT * FROM mytable
WHERE a AND ((b AND c) OR (d AND e) OR (f AND g));

Arel似乎对第二种形式不太宽容。我已经非常接近以下范围了:

scope :needs_any_delivery, lambda {
  table = self.arel_table
  common_assert = table[:a].eq(true)
  where(
    common_assert.and(
      table[:b].eq(true).and(
        table[:c].eq(false)
      )
    ).or(
      common_assert.and(
        table[:d].eq(true).and(
          table[:e].eq(false)
        )
      ).or(
        common_assert.and(
          table[:f].eq(true).and(
            table[:g].eq(false)
          )
        )
      )
    )
  )
}

这会产生以下查询:

SELECT * FROM mytable
WHERE (
  (a = 't' AND b = 't' AND c = 'f'
    OR (a = 't' AND d = 't' AND e = 'f' OR a = 't' AND f = 't' AND g = 'f')
  )
)

已关闭,但第三个AND组未与第二个AND组分开。我发现如果我在第三组的末尾添加一些额外的伪or子句,那么Arel会自己对第三个子句进行适当的分组......但这看起来像是一个黑客。

想知道是否有任何铁路/野外专家有任何想法。谢谢!

2 个答案:

答案 0 :(得分:1)

除非我读错了,否则使用active_record_or之类的内容可能更容易,而不是直接使用arel。

使用该gem,你应该能够得到正确的结果:

common_assert = where(a: true) # WHERE a
option_one = where(b: true).where(c: true) # b AND c
option_two = where(d: true).where(e: true) # d AND e
option_three = where(f: true).where(g: true) # f AND g
combined_optionals = option_one.or.option_two.or.option_three # (b AND c) OR (d AND e) OR (f AND g)
common_assert.merge(combined_optionals) # WHERE a AND ((b AND c) OR (d AND e) OR (f AND g))

答案 1 :(得分:0)

您还可以使用

def dnf(clauses)
  clauses
    .map { |clause| clause.reduce(:and) }
    .reduce(:or)
end

table = Arel::Table.new(:some_fancy_table)
table[:a].eq(true).and dnf [
  [table[:b].eq(true), table[:c].eq(false)],
  [table[:d].eq(true), table[:e].eq(false)],
  [table[:f].eq(true), table[:g].eq(false)],
]

另外,我认为您实际上并不需要围绕和连接子句的括号,请参阅SQL Logic Operator Precedence: And and Or