Rails SQL"选择"跨越几列:其中(code1,code2)in((" A",1),(" A",3),(" Q",9) )

时间:2018-05-03 18:21:07

标签: sql ruby-on-rails postgresql activerecord

我有业务要求根据一个表中的两个字段选择记录:code1和code2。选择是复杂和硬编码的,没有可编码的押韵或理由,并且包括表中实际存在的一百对中的十二对。

  • C,1
  • C,2
  • J,9
  • Z,0

请注意,还有其他" C"表中的代码,例如(C,3)。没有组合字段将它们捕获为值,例如" C3"。

SQL支持这样的查询:Two columns in subquery in where clause例如

SELECT * from rejection_codes
  where (code1, code2) in (("A", 1), ("A", 3), ("Q", 9))

有没有办法使用Rails和ActiveRecord的ORM执行此操作,而无需使用原始SQL?

我正在使用Postgres运行Rails 4.2.9,如果重要的话。

*为什么不要...... *

添加字段:我无法控制数据库架构。如果我这样做,我会添加一个新列作为该组的标志。或者是将值连接成字符串的计算列。或者其他......但我不能。

使用原始SQL:是的......如果我不能通过ORM进行,我可能会这样做。

2 个答案:

答案 0 :(得分:1)

如果你想要那个结构,那么你可以这样做:

pairs = [['A', 1], ['A', 3], ['Q', 9]]
RejectionCode.where('(code1, code2) in ((?), (?), (?))', *pairs)

当然,pairs.length可能并不总是三个,所以你可以说:

pairs = [['A', 1], ['A', 3], ['Q', 9]]
placeholders = (%w[(?)] * pairs.length).join(', ')
RejectionCode.where("(code1, code2) in (#{placeholders})", *pairs)

是的,那是使用字符串插值来构建SQL片段,但在这种情况下它非常安全,因为您正在构建所有字符串并且您确切地知道它们中的内容。如果你把它放到一个范围内,那么至少丑陋会被隐藏起来,你可以用你的测试套件轻松覆盖它。

或者,您可以利用一些等价物。 in是一个花哨的or所以这些大致相同:

c in (x, y, z)
c = x or c = y or c = z

和记录(甚至是匿名的)逐列进行比较,因此它们是等价的:

(a, b) = (x, y)
a = x and b = y

这意味着像这样:

pairs = [['A', 1], ['A', 3], ['Q', 9]]
and_pair = ->(a) { RejectionCode.where('code1 = ? and code2 = ?', *a) }
and_pair[pairs[0]].or(and_pair[pairs[1]]).or(and_pair[pairs[2]])

应该给你相同的结果。或者更一般地说:

pairs = [['A', 1], ['A', 3], ['Q', 9], ... ]
and_pair = ->(a) { RejectionCode.where('code1 = ? and code2 = ?', *a) }
query = pairs[1..-1].inject(and_pair[pairs.first]) { |q, a| q.or(and_pair[a]) }

同样,你想在范围内隐藏这种丑陋。

答案 1 :(得分:0)

*这是一个不错的解决方法,但不完全是ORM问题的解决方案*

未能在ActiveRecord中找到正确的方法,我猜想,希望最好:

class ApprovalCode < ActiveRecord::Base

  REJECTION_CODES = [
    ['A', '0'],
    ['R', '1'],
    ['R', '5'],
    ['R', '6'],
    ['X', 'F'],
    ['X', 'G']
  ]

  scope :rejection_allowed, -> { where([:code, :sub_code], REJECTION_CODES) }  # This didn't work.

end

那不起作用。所以,我在范围中使用了原始SQL,这确实有效:

  scope :rejection_allowed, -> { where("(code, sub_code) in (#{rejection_list})") }

  def self.rejection_list
    REJECTION_CODES
      .map{|code, sub_code| "('#{code}', '#{sub_code}')"}
      .join(', ')
  end

我仍然希望在ORM中找到如何做到这一点,或者阅读有关完全不同的问题方法的建议。由于它全部封装在范围和常量中,因此稍后重构将是微不足道的,并且保持常量和范围分离将允许无痛测试。