我正在尝试在我的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会自己对第三个子句进行适当的分组......但这看起来像是一个黑客。
想知道是否有任何铁路/野外专家有任何想法。谢谢!
答案 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