ActiveRecordで別モデルをUnionした後更にsumやgroup_byで束ねる方法

## 環境
- Ruby2.5.1
- Rails 5.2.2
- mysql 5.6.34


## 作りたいSQL
※若干もともと書いたコードから諸々変更しているため、そのまま実装すると問題があるかもしれません。
以下のSQLを書いたとしましょう

```sql
SELECT date,
       union_reports.product_id,
       SUM(stockings_num) AS stockings_num,
       SUM(sales_num) AS sales_num,
FROM
  (SELECT Date(stocked_at) AS date,
          product_id,
          SUM(num) AS stockings_num,
          0 AS sales_num,
   FROM `stockings`
   WHERE stocked_at BETWEEN '20180101000000' AND '20181231235959'
     AND product_id = 1
   GROUP BY date(delivered_at),
   UNION SELECT Date(saled_at) AS date,
                product_id,
                0 AS stockings_num,
                SUM(num) AS sales_num,
   FROM `sales`
   WHERE saled_at BETWEEN '20180101000000' AND '20181231235959'
     AND product_id = 1
   GROUP BY date(saled_at)) union_reports
GROUP BY date
```
前提としてProductsテーブルに商品に関する情報が何かしらあり、StockingsテーブルとSalesテーブルにそれぞれ商品と売れた（仕入れた）数、そのイベントの起きた日付を記録しています。
SQLはproductsモデルにおいてidが１の2018年内の仕入れ数と売上個数を見たいというわけですね。

きちんとSQLを見ると、まず両モデルで2018年内+product_idが１のレコードを抽出、sumとgroup_byでまとめてunionしたものをunion_reportsとして定義、さらにそれをまとめている形です。

## ActiveRecord化
分解してみていきましょう。
まず以下のSQL。

```sql
SELECT Date(stocked_at) AS date,
          product_id,
          SUM(num) AS stockings_num,
          0 AS sales_num,
   FROM `stockings`
   WHERE stocked_at BETWEEN '20180101000000' AND '20181231235959'
     AND product_id = 1
   GROUP BY date(delivered_at)
```
こちらはActiveRecordだと以下で書けると思います。（stocked_atの部分は元のSQLだとjoinsの条件でSQLベタがきだったのをwhereに落としているので不都合があるかもしれません。）

```ruby
def make_stock_statement
  Stocking.select("Date(stocked_at) AS date", "product_id",
         "SUM(num) AS stockings_num","0 AS sales_num").group("product_id")
         .where(stocked_at: '20180101000000'..'20181231235959').where(product_id: 1)
end
```
同様に以下のSQLもActiveRecordにしましょう。

```sql
   UNION SELECT Date(saled_at) AS date,
                product_id,
                0 AS stockings_num,
                SUM(num) AS sales_num,
   FROM `sales`
   WHERE saled_at BETWEEN '20180101000000' AND '20181231235959'
     AND product_id = 1
   GROUP BY date(saled_at)
```
以下のようになると思います。

```ruby

def make_sale_statement
  Sale.select("Date(saled_at) AS date", "product_id",
         "SUM(num) AS sales_num","0 AS stock_num").group("product_id")
         .where(saled_at: '20180101000000'..'20181231235959').where(product_id: 1)
end
```
二つのサブクエリをUnionします。(詳細はこの記事参照[^1])

```ruby
def make_union_reports
  Arel::Nodes::Union.new(
          make_delivery_statement.ast,
          make_request_statement.ast
        ).to_sql
end
```
最後にさらにsumしますが、union_reportsを参照したいからといって

```ruby
def make_statement
      UnionReport.select("date",
       "union_reports.product_id",
       "SUM(stockings_num) AS stockings_num",
       "SUM(sales_num) AS sales_num",)
        .from("#{make_union_reports} union_reports").group("date")
end
```
としてはいけません。union_reportsはテーブルとしては存在しないのでエラーを出します。
じゃあどうすればいいか。

```ruby
def make_statement
      Sale.select("date",
       "union_reports.product_id",
       "SUM(stockings_num) AS stockings_num",
       "SUM(sales_num) AS sales_num",)
        .from("#{make_union_reports} union_reports").group("date")
end
```
単にActiveRecordを使いたいだけなので適当なモデルを最初に持って来ればいいんですね。
ActiveRecord.select(以下略)でもいけるかもしれません。（未検証）

## 追記
途中で一部Arelを使っているんですが、そろそろ来そうなRails6.0.0では途中で一瞬出てくる

```ruby
make_delivery_statement.ast,
make_request_statement.ast
```
の部分は

```ruby
make_delivery_statement.arel.ast,
make_request_statement.arel.ast
```
とastの前にarelを追加する必要があるみたいです。
## 参考文献
[^1]: https://qiita.com/takaki_k/items/c01f5eab5f7a0676f130