以下のように複数カラムをつかってINで検索することを行値式
や行値構成子
と呼ぶそうです。
SELECT * FROM table
WHERE (column1, column2) IN (
(value1, value2),
(value3, value4)
);
これをRailsで使用する方法がなさそうだったので、実現する方法を考えました。
ActiveRecord.where
は
Hoge.where('id = ? OR id = ?', 1, 2)
=> SELECT `hoges`.* FROM `hoges` WHERE (id = 1 OR id = 2)
のように文字列でWHERE文を使用しつつ、プレースホルダも使えるので、これを利用します。
pluckなどで取得した値をそのまま使えるようにしたかったので、
第1引数にカラム名の配列
第2引数に値の配列
を指定してwhereに渡す値を生成するメソッドを定義します。
# @param [Array] columns 検索に使用するカラム名の配列 ex. [:id, :name]
# @param [Array] values 検索に使用する値の2重配列 ex. [[1, 'hoge'], [2, 'piyo']]
# @return [Array] ActiveRecord.whereで使用できるWHERE句と検索値を持った配列
def row_constructor(columns, values)
# 1レコード分のプレースホルダを作成
# ex. [:id, :name] => (?,?)
placeholder = "(#{(['?'] * columns.size).join(',')})"
# 検索数分のプレースホルダを作成
# ex. [[1, 'hoge'], [2, 'piyo']] => (?,?),(?,?)
placeholders = ([placeholder] * values.size).join(',')
# 行値式のWHERE句を作成
# ex. (id,name) IN ((?,?),(?,?))
sql = "(#{columns.join(',')}) IN (#{placeholders})"
# ActiveRecord.whereには可変長引数で渡すので2重配列を平坦にして、先頭にクエリ文字列を追加
values.flatten.unshift(sql)
end
上記のメソッドを以下のように使用します。
columns = [:id, :name]
values = [[1, 'hoge'], [2, 'piyo']]
Hoge.where(*row_constructor(columns, values))
=> SELECT `hoges`.* FROM `hoges` WHERE ((id,name) IN ((1,'hoge'),(2,'piyo')))
そもそもActiveRecordにメソッドが用意されていたり、
gemがあったら教えてください。