Ecto 3.0: クエリーAPIの改善を予定[後編]

gumitech

gumi TECH

Posted on October 16, 2018

Ecto 3.0: クエリーAPIの改善を予定[後編]

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

UNIONとEXCEPTおよびINTERSECT

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

customer_city_query = Customer |> select([c], c.city)
Supplier |> select([s], s.city) |> union(customer_city_query)
Enter fullscreen mode Exit fullscreen mode

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

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

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

Timofey Martynov氏のプルリクエスト「Add UNION and UNION ALL support」により状況は変わりました。UNIONUNION ALLEcto.Queryのフィールドとしてだけ扱うというのです。同じように、ORDER BYsLIMITWHEREなども含められます。はじめ、この方向でよいのか疑問がありました。けれど、SQLの仕様を読み直すと、UNIONUNION ALLモデルとして正しいことがわかったのです。

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

SELECT city FROM suppliers UNION SELECT city FROM customers LIMIT 10
Enter fullscreen mode Exit fullscreen mode

このクエリと同じ意味になるのは、つぎの[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についてつぎのように説明します(PostgreSQL 9.5.4文書「SELECT」「UNION句」参照)。

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

select_statement UNION [ ALL | DISTINCT ] select_statement

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

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

WINDOWとOVERのサポート

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

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

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

from e in Employee,
select: {e.depname, e.empno, e.salary, avg(e.salary) |> over(partition_by: e.depname)}
Enter fullscreen mode Exit fullscreen mode

第1引数はアグリゲータかWINDOW関数のいずれかです。デフォルトでは、PostgreSQLとMySQLの組み込み関数をすべてサポートしています。関数が備わるモジュールはEcto.Query.WindowAPIです(ドキュメンテトはまだリリースされていません)。

Anton氏の貢献によるもので、議論は「OVER, PARTITION BY and WINDOW」で確かめられます。

その他の変更

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

select: coalesce(p.title, p.old_title)
Enter fullscreen mode Exit fullscreen mode

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

p.field1 |> coalesce(p.field2) |> coalesce(p.field3)
Enter fullscreen mode Exit fullscreen mode

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

select: filter(count(), p.public == true)
Enter fullscreen mode Exit fullscreen mode

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

order_by: [desc_nulls_first: p.title]
Enter fullscreen mode Exit fullscreen mode

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

関連記事

💖 💪 🙅 🚩
gumitech
gumi TECH

Posted on October 16, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related