Рельсы SQL "выбрать в" через несколько столбцов: где (code1, code2) in (("A", 1), ("A", 3), ("Q", 9)) - PullRequest
0 голосов
/ 03 мая 2018

У меня есть бизнес-требование для выбора записей на основе двух полей в одной таблице: code1 и code2. Выбор является сложным и жестко запрограммированным, без кодируемой рифмы или причины и включает в себя около дюжины пар из сотен пар, которые фактически существуют в таблице.

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

Обратите внимание, что в таблице есть другие коды "C", например (C, 3). Нет объединенного поля, которое фиксирует их оба в качестве значения, например, «C3».

SQL поддерживает запрос, подобный следующему: Два столбца в подзапросе, где предложение , например.

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

Есть ли способ сделать это с помощью Rails и ActiveRecord ORM, не прибегая к необработанному SQL?

Я использую Rails 4.2.9 с Postgres, если это имеет значение.

* Почему ты не ... *

Добавить поле: у меня нет контроля над схемой базы данных. Если бы я это сделал, я бы добавил новый столбец в качестве флага для этой группы. Или вычисляемый столбец, который объединяет значения в строку. Или что-то ... Но я не могу.

Использовать сырой SQL: Да ... Я мог бы сделать это, если не смогу сделать это через ORM.

Ответы [ 2 ]

0 голосов
/ 03 мая 2018

Если вы хотите именно эту структуру, вы можете сделать что-то вроде этого:

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]) }

Опять же, вы хотели бы скрыть это уродство в области видимости.

0 голосов
/ 03 мая 2018

* Это достойный обходной путь, но не совсем решение вопроса 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 или прочитать предложения по совершенно другим подходам к проблеме. Поскольку все это инкапсулировано в области видимости и константе, позднее будет легко выполнить рефакторинг, а сохранение констант и области видимости позволит проводить безболезненные тесты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...