Ecto 3.0: クエリーAPIの改善を予定[後編]
Tags: elixir, webdev, progarmming, tutorial
Published: 2018-10-16 00:49:02

---
title: Ecto 3.0: クエリーAPIの改善を予定[後編]
published: true
description: Ecto 3.0のリリースに先立ち、plataformatec社開発チームのblog記事「A sneak peek at Ecto 3.0 - query improvements (part 2)」が公開されました。この記事にもとづいて、先週の「Ecto 3.0 - クエリーAPIの改善を予定[前編]」に引き続き、もっとも大きな改善項目であるEcto.Query APIについてご紹介します。
tags: #elixir, #webdev, #progarmming, #tutorial
---

Ecto 3.0のリリースに先立ち、plataformatec社開発チームのblog記事「[A sneak peek at Ecto 3.0: query improvements (part 2)](http://blog.plataformatec.com.br/2018/10/a-sneak-peek-at-ecto-3-0-query-improvements-part-2/)」が公開されました。本稿はplataformatec社の許諾によりこの記事にもとづいて、先週の「[Ecto 3.0: クエリーAPIの改善を予定[前編]](https://dev.to/gumi/ecto-30-api-d0g)」に引き続き、もっとも大きな改善項目である`Ecto.Query` APIについてご紹介します。


# UNIONとEXCEPTおよびINTERSECT
Ecto 3.0では、`UNION`と`EXCEPT`および`INTERSECT`がクエリに加えられるようになりました。たとえば、顧客と供給者の両方の都市はつぎのようにして得られます。

```elixir
customer_city_query = Customer |> select([c], c.city)
Supplier |> select([s], s.city) |> union(customer_city_query)
```

`union`は重複を除こうとするため、負荷が高くなることに注意してください。多くの場合、重複がないとわかっているか、あっても気にしないときは、`union_all`を使う方がよいでしょう。

Ectoの機能として`union`に対応してほしいという要望は、たびたび上がっていました。けれど、これまでこの機能を実装するために採られてきたやり方は、方向が誤っていたようです。それは、ふたつのクエリを統合する新しいデータ型が備えられなければならないと想定していたことです。

つまり、考えられていたのは`union(query1, query2)`が`Ecto.UnionQuery{left: query1, right: query2}`というような新たな構造体を返すことでした。これでは、Ectoユーザーはさまざまな型のクエリを扱わなければならず、複雑さを与えてしまいかねません。

[Timofey Martynov](https://github.com/feymartynov)氏のプルリクエスト「[Add UNION and UNION ALL support](https://github.com/elixir-ecto/ecto/pull/2678)」により状況は変わりました。`UNION`と`UNION ALL`を`Ecto.Query`のフィールドとしてだけ扱うというのです。同じように、`ORDER BYs`や`LIMIT`、`WHERE`なども含められます。はじめ、この方向でよいのか疑問がありました。けれど、SQLの仕様を読み直すと、`UNION`と`UNION ALL`モデルとして正しいことがわかったのです。

つぎのSQLクエリを考えてみましょう。

```elixir
SELECT city FROM suppliers UNION SELECT city FROM customers LIMIT 10
```

このクエリと同じ意味になるのは、つぎの[a]と[b]のどちらでしょう。

[a]`(SELECT city FROM suppliers) UNION (SELECT city FROM customers LIMIT 10)`
[b]`SELECT city FROM suppliers UNION (SELECT city FROM customers) LIMIT 10`

非公式なアンケートをとったところ、多くの人が[a]を選びました。`UNION`がトップレベルの低優先順位の演算子として働くと考えたためでしょう。けれど、正解は[b]です。PostgreSQLのドキュメントも、[`UNION`句](https://www.postgresql.org/docs/9.5/static/sql-select.html#SQL-UNION)についてつぎのように説明します(PostgreSQL 9.5.4文書「[SELECT](https://www.postgresql.jp/document/9.5/html/sql-select.html)」「UNION句」参照)。

> `UNION`句の一般的な構文はつぎのとおりです。

> `select_statement UNION [ ALL | DISTINCT ] select_statement`

> `select_statement`は`SELECT`文のうち、`ORDER BY`、`LIMIT`、`FOR NO KEY UPDATE`、`FOR UPDATE`、`FOR SHARE`、`FOR KEY SHARE`のいずれの句もないものです(ただし、`ORDER BY`と`LIMIT`はかっこで囲めば部分式として与えることはできます。かっこがないと、これらの句は、右辺の入力式ではなく、`UNION`の結果に適用されます)。

いいかえれば、`UNION`と`INTERSECT`および`EXCEPT`は`WHERE`のようにモデル化しなければなりません。与えられたクエリの句とみなされ、トップレベルの操作ではないからです。これは、まさにEctoにおける実装です。


# WINDOWとOVERのサポート
Ecto 3.0はついに`WINDOW`句と`OVER`演算子に対応し、多くの`WINDOW`関数も備えました。たとえば、従業員の給与とその平均を部門内で比べてみます。

```elixir
from e in Employee,
select: {e.depname, e.empno, e.salary, avg(e.salary) |> over(:department)},
windows: [department: [partition_by: e.depname]]
```

`over/2`演算子は、第2引数としてウィンドウ名かウィンドウ式を受け取ります。つぎのクエリでも結果は同じです。

```elixir
from e in Employee,
select: {e.depname, e.empno, e.salary, avg(e.salary) |> over(partition_by: e.depname)}
```

第1引数はアグリゲータか`WINDOW`関数のいずれかです。デフォルトでは、PostgreSQLとMySQLの組み込み関数をすべてサポートしています。関数が備わるモジュールは[`Ecto.Query.WindowAPI`](https://github.com/elixir-ecto/ecto/blob/master/lib/ecto/query/window_api.ex)です(ドキュメンテトはまだリリースされていません)。

[Anton氏](https://github.com/Anber)の貢献によるもので、議論は「[OVER, PARTITION BY and WINDOW](https://github.com/elixir-ecto/ecto/pull/2618)」で確かめられます。


# その他の変更
`Ecto.Query`にはほかにも変更が加えられました。たとえば、`coalesce`の対応が組み込まれました。

```elixir
select: coalesce(p.title, p.old_title)
```

パイプ演算子とも組み合わせられます。

```elixir
p.field1 |> coalesce(p.field2) |> coalesce(p.field3)
```

また、`FILTER`式にも対応し、アグリゲータの値をフィルタリングできます。

```elixir
select: filter(count(), p.public == true)
```

さらに、`order_by`が`asc_nulls_last`や`：asc_nulls_first`、`：desc_nulls_last`、および`：desc_nulls_first`をサポートするようになりました。順序づけのとき、`NULL`がいつ返るか正確に定められます。`:desc`と`:asc`を使っていれば、Ecto 2.0と同じ動きで、データベースに依存します。

```elixir
order_by: [desc_nulls_first: p.title]
```

今後も、バフォーマンスの改善や、移行のしやすさなどについて、情報が公開されるようです。


### 関連記事
- [Ecto 3.0: 互換性を破る変更予定について](https://dev.to/gumi/ecto-30--523l)
- [Ecto 3.0: クエリーAPIの改善を予定[前編]](https://dev.to/gumi/ecto-30-api-d0g)
- [Ecto 3.0: パフォーマンスと新バージョンへの移行など](https://dev.to/gumi/ecto-30--3ema)