はじめに
あるプロジェクトの開発中、「Arel」というSQL文的なものが出てきました。
初めて見た僕は、「なにこれSQL文?」と疑問でした。調べてみると、Railsの簡単にSQLを使えるという機能の一つということがわかりました。
そこで、今後も使う可能性がありそうだったので、記事にまとめようと思った次第です。。
備忘録的にまとめてますが、誰かの為になれたらと思います。
Arelとは
Arelとは,「Relational Algebra」または「Active Relation」の略で、
その名前の通り、ActiveRecordから派生した、
「関係代数」をRubyのオブジェクトで取り扱うライブラリのことです。
公式ドキュメントによると、、
1.複雑なSQLクエリを簡単に生成可能
2.色んなRDBMSに対応可能
3.フレームワーク(特にORM)のフレームワークとして開発された
つまり、Arelを使うことで、DBの互換性やSQL文字列の生成などに時間を取られることなく、
大事な設計やモデリングに注力してO/Rマッピング処理を実装することができるようになっています。
基本的な操作方法一覧
arel_table
モデル名.arel_table
とすることで指定したモデルのテーブルを検索する準備が出来る。
オプションとして、カラム名を追加したり、条件を追加して検索できる。
Staff.where( Staff.arel_table[:age] )
Arel.sql(〇〇〇〇)
Arel.sql(〇〇〇〇)
とすることでSQL文を生成できる。
※〇〇にはSQL文を入れる。
Arel.sql('〇〇〇〇')
Arel::Nodes
Arel::Nodes#build_quoted
文字列などの値をラップして、Arel::Nodes::NamedFunction
などの引数に渡せるようにします。
Arel::Nodes.build_quoted(" ")
Arel::Nodes::NamedFunction
任意のSQL関数の呼び出しを表現するために使う。
第1引数に関数名、第2引数に関数に渡す引数(配列)、第3引数にエイリアス名(任意)を渡す。
staffs_table = Staff.arel_table
date_format = Arel::Nodes.build_quoted("%Y-%m-%d")
Staff.select(
Arel::Nodes::NamedFunction.new(
"DATE_FORMAT", # 日時を指定のフォーマットに整形してくれる関数
[staffs_table[:created_at], date_format], # created_atを指定したフォーマットに変換
"registration_date"
)
)
Arel::Nodes::SqlLiteral
生のSQL文字列を生成出来る。
SQLの構文中に必要な文字列をラップするのに使われる。
- utf8_general_ci
- UTF-8文字コードで半角や全角、濁点を区別し、英語の大文字小文字を区別しない指定。
Arel::Nodes::SqlLiteral.new("utf8_general_ci")
Arel::Nodes::InfixOperation
「infix operation」は二項演算という意。
2つの値・変数の計算を行うことができます。
第1引数に演算子、第2引数に演算子の左側の値、第3引数に演算子の右側の値が入る。
Staff.where(
Arel::Nodes::InfixOperation.new(
"COLLATE", # 検索条件で照合順序を指定
Staff.arel_table[:name], # 演算子の左側が、staffsテーブルのnameカラム
Arel::Nodes::SqlLiteral.new("utf8_general_ci") # 演算子の右側が、utf8_genera_ci
)
.matches(Arel::Nodes.build_quoted("さとう%")) # nameが「さとう」から始まるStaffを取得
)
Arel::Nodes::OuterJoin
外部結合(OUTER JOIN)する際に使用する。
JOIN句の第2引数に Arel::Nodes::OuterJoin
を指定し、
最後にjoin_sources を呼び出すことで結合できる。
comments_table = Comment.arel_table
staffs_table = Staff.arel_table
Comment.joins(
comments_table
.join(staffs_table, Arel::Nodes::OuterJoin)
.on(
comments_table[:staff_id].eq(staffs_table[:id])
.and(staffs_table[:is_active].eq(true))
)
.join_sources
)
※commentsテーブルのstaff_idとStaffテーブルのidが等しい、
かつstaffsテーブルの is_activeがtrueのものという条件。
Arel::Nodes::Descending, Arel::Nodes::Ascending
Arel::Nodes::Descending
は ORDER 句の DESC (降順)の部分を担当。
Arel::Nodes::Ascending
は ASC (昇順)です。引数には、ORDER BY の右側の値が入ります。
Staff.order(
Arel::Nodes::Descending.new(
Staff.arel_table[:staff_type]) # staff_typeの降順
)
)
Arel::Nodes::Case
SQL の CASE 構文を表現することができます。
- gt
- 左項の値が右項の値より大きいときに真になる。(「>」と同意)
staffs_table = Staff.arel_table
new_cond = staffs_table[:created_at].gt(Time.zone.now - 3.days)
Staff.order(
Arel::Nodes::Ascending.new(
Arel::Nodes::Case.new
.when(new_cond).then(1)
.else(9)
)
)
Staffが作成されて3日以内であれば 1、そうでなければ 9 という値とする。
3日以内に作成されたStaffを昇順で並び替えている。
Arel::Nodes::Grouping
括弧()で囲む。四則計算等で括弧を使いたい場合に使う。
Arel::Nodes::Grouping( object )
Arel::Nodes::As
AS による別名定義の文 〇 AS ×
を作る。
Staff.select( Staff.arel_table[:id].as('no') )
Tips集
使えるTipsをご紹介。
IS NOT NULL
Staff.where(
Staff.arel_table[:age].not_eq(20)
)
AS
Staff.where(
Staff.arel_table[:name].as('admin')
)
admin = Staff.arel_table.alias('admin')
Comment.joins(admin).select(admin[:name])
サブクエリ
FROM句へのサブクエリ
new_table = Arel::Table.new(nil)
sql = new_table.from(
Staff.where(created_at: start_at...end_at).to_sql
)
.project(Arel.sql('*'))
.to_sql
JOIN句へのサブクエリ
sub_query = Staff.where(created_at: start_at...end_at).to_sql
Comment.joins("INNER JOIN (#{sub_query})")
SUM
Staff.where(created_at: start_at...end_at)
.select(Staff.arel_table[:name].sum().as('admin'))
Where
staffs = Staff.arel_table
staffs.arel_table.project(Arel.sql('*')).where(staffs[:id].eq(1)).to_sql
order by
staffs = Staff.arel_table
staffs.project(Arel.sql('*')).order(staffs[:id].asc).to_sql
集計関数とgroup by
staffs = Staff.arel_table
staffs.project(staffs[:id].count, staffs[:name]).group(staffs[:age]).to_sql
COUNTで利用するDISTINCT
Staff.select(Staff.arel_table[:id].count())
.select(Staff.arel_table[:id].count('distinct'))
終わりに
Arelでは色んな使い方があるのだなと調べて驚きました。
使いこなすには実践あるのみ!ですね。。
しかし、使うことに難を示す人たちもいるようで、賛否両論ではありますが、
チーム開発において、誰かが使って自分が理解出来ない。。ってことが起きると開発にブレーキをかけてしまうので、一応理解しているだけ得だと思いました!
参考
[Arel::Nodes を使って Arel で複雑な SQL文を作っちゃおう]
(https://qiita.com/Ping/items/87148251fe9bec2a847e)
[Arel::Nodesを最低限読めるようになりたい]
(https://qiita.com/akitoshi-n/items/00ea99a685863de60683)
[RailsのArelのTips]
(https://qiita.com/yamagen0915/items/b1721a9d1ea076f8cdc5)
[RailsのArelを調査してみた]
(https://qiita.com/tetz-akaneya/items/9ac200acbccc46b2ea44)
[Arelで色んなSQLを組み立ててみる]
(https://ryopeko.hatenablog.com/entry/20101215/1292373612)
[ActiveRecordのarel_tableから作れる条件式まとめ]
(https://qiita.com/akicho8/items/c3bfd39213d6e9843e4f)
[RailsでArel、早見表]
(https://qiita.com/k-fujino/items/09f8baf790abd49308f4)