3

我正在尝试在我的 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 会适当地将第三个子句单独分组……但这似乎是一个 hack。

想知道那里是否有任何 Rails/arel 大师有任何想法。谢谢!

4

3 回答 3

4

如果您像我一样并且真的想继续使用 Arel 来实现此功能,我发现创建新or方法似乎是最佳途径。

我添加了一个名为arel_fixed_or.rb以下内​​容的新初始化程序:

Arel::Nodes::Node.class_eval do

  ###
  # Factory method to create a Nodes:Or that has a Nodes::Grouping
  # node as a child.
  def fixed_or right
    Arel::Nodes::Or.new self, Arel::Nodes::Grouping.new(right)
  end

end

在您通常想要使用or语句的任何地方,您都可以继续使用fixed_or它,然后将一个 or-ed 分组语句附加到您的子句的末尾,这对我来说应该是预期的方法。Arel::Nodes::Grouping如果这对您没有意义,请随意删除该部分。

需要注意的一件事是,您可能仍然需要手动放置分组,以便正确的元素被括号括起来。例如:

table = Arel::Table.new(:mytable)
common_assert = table[:a].eq(true)

first_or = table[:b].eq(true).and(table[:c].eq(true))
second_or = table[:d].eq(true).and(table[:e].eq(true))
third_or = table[:f].eq(true).and(table[:g].eq(true))

common_assert.and(
  table.grouping(
    table.grouping(first_or)
      .fixed_or(second_or)
      .fixed_or(third_or)
  )
)

它的to_sql输出变为:

"mytable"."a" = 't' AND (
  ("mytable"."b" = 't' AND "mytable"."c" = 't') OR
  ("mytable"."d" = 't' AND "mytable"."e" = 't') OR
  ("mytable"."f" = 't' AND "mytable"."g" = 't')
)
于 2015-09-10T18:46:38.103 回答
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))
于 2013-02-23T07:10:15.657 回答
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)],
]

另外,我认为您实际上不需要在 and-connected 子句周围加上括号,请参阅SQL Logic Operator Precedence: And and Or

于 2018-04-16T16:27:28.110 回答