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では、UNION
とEXCEPT
および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」により状況は変わりました。UNION
とUNION ALL
をEcto.Query
のフィールドとしてだけ扱うというのです。同じように、ORDER BYs
やLIMIT
、WHERE
なども含められます。はじめ、この方向でよいのか疑問がありました。けれど、SQLの仕様を読み直すと、UNION
とUNION 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_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
関数も備えました。たとえば、従業員の給与とその平均を部門内で比べてみます。
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_by
がasc_nulls_last
や:asc_nulls_first
、:desc_nulls_last
、および:desc_nulls_first
をサポートするようになりました。順序づけのとき、NULL
がいつ返るか正確に定められます。:desc
と:asc
を使っていれば、Ecto 2.0と同じ動きで、データベースに依存します。
order_by: [desc_nulls_first: p.title]
今後も、バフォーマンスの改善や、移行のしやすさなどについて、情報が公開されるようです。
Top comments (0)