商品Aと商品Bの両方を注文した顧客の一覧を取得したい。顧客には複数の注文が紐付いていて、注文はひとつの商品を持つ。
上のような場合のSQLとArelの書き方を試行錯誤していて、なんとかできたのでメモしておきます。
SQL
select *
from customers
where id in (
select customer_id
from orders
where item_id in (1,2) /* 指定する商品ID */
group by customer_id
having count(distinct item_id) = 2 /* 指定する商品IDの数 */
)
複数の商品ID(1,2)で注文テーブルを絞り込んで、指定した商品だけの注文にします。
と同時に顧客IDで集計して、商品IDが指定された数だけあるものを抽出しています。
Arel
app/models/Customer.rb
# 引数の商品全てを注文したことがある
scope :has_item_id_all, -> item_ids {
customer = arel_table
order = Order.arel_table
subquery = order.project(order[:customer_id])
.where(order[:item_id].in(item_ids))
.group(order[:customer_id])
.having(order[:item_id].count('distinct').eq(item_ids.size))
where(customer[:id].in(subquery))
}
SQL文ををArelの文法に変えただけですが、サブクエリの書き方がわからずにひどく苦労しました。
Arelを使ったことで他スコープとのマージもできるようになりました。