loading...
gumi TECH Blog

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

gumitech profile image gumi TECH ・2 min read

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)

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

このクエリと同じ意味になるのは、つぎの[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]]

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

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です(ドキュメンテトはまだリリースされていません)。

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

その他の変更

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

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

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

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

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

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

さらに、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]

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

関連記事

Posted on by:

gumitech profile

gumi TECH

@gumitech

gumi TECH は、株式会社gumiのエンジニアによる技術記事公開やDrinkupイベントなどの技術者交流を行うアカウントです。 gumi TECH Blog: http://dev.to/gumi / gumi TECH Drinkup: http://gumitech.connpass.com

gumi TECH Blog

株式会社gumiのエンジニアによる技術記事を公開しています。

Discussion

pic
Editor guide