ActiveRecord / Arel
以下の続編としてお読みください
Railsの複雑な検索はスコープを使おう
RailsでSQL文をどんどん部品化していきます
今回DatabaseがRedshift(Postgresqlに似ている)です
Arel::Nodes::NamedFunction
DB固有の関数を使う場合などはこれ
# こんな感じのテーブルがあるよ
# create table timeaxis (time_s timestamp)
time_table = Arel::Table.new('timeaxis')
time_table.project(
Arel::Nodes::NamedFunction.new(
'date_trunc',
[Arel::Nodes::build_quoted('day'),
time_table[:time_s]]
).as('daily')
).group(
Arel::Nodes::NamedFunction.new(
'date_trunc',
[Arel::Nodes::build_quoted('day'),
time_table[:time_s]]
)
)
# 得られるSQL
# SELECT
# date_trunc('day', "time_table"."time_s") AS daily
# FROM
# "time_table"
# GROUP BY
# date_trunc('day', "time_table"."time_s")
タイムスタンプを日次にする例、scopeとして宣言しておけば、day
の部分を引数にして1時間ごと、月ごとなど変更は容易です
NamedFunction
は第一引数を関数名として、第二引数に配列(中身はArelオブジェクト)をとりカンマ区切りでつなぐようです。第二引数の配列内には2つを超えるオブジェクトも渡せます
build_quoted
は文字列をシングルクオートで囲むみたい
PostgresqlのCASTをスコープに設定するなら
scope :cast, lambda { |str, type|
Arel::Nodes::NamedFunction.new(
'CAST', [
Arel::Nodes::As.new(
Arel::Nodes.build_quoted(str),
Arel::Nodes::SqlLiteral.new(type)
)
]
)
}
# cast('20.2', 'float') => CAST('20.2' AS float)
# cast('1 HOUR', 'interval') => CAST('1 HOUR' AS interval)
文字列と変換する型を引数として渡せば型変換してくれます
As
は二つの引数をAS
でつないでくれるみたい
SqlLiteral
は渡した文字列をArelオブジェクトにしてくれる
Arel::Nodes::InfixOperation
あまり使い道は見いだせませんが、こんなことも可能です
Arel::Nodes::NamedFunction.new(
'SELECT',
[Arel::Nodes::InfixOperation.new(
'+',
Arel::Nodes::NamedFunction.new(
'CAST', [
Arel::Nodes::As.new(
Arel::Nodes.build_quoted('2015-08-01'),
Arel::Nodes::SqlLiteral.new('TIMESTAMP')
)
]
),
Arel::Nodes::InfixOperation.new(
'*',
Arel::Nodes::NamedFunction.new(
'GENERATE_SERIES', [0, 23]
),
Arel::Nodes::NamedFunction.new(
'CAST', [
Arel::Nodes::As.new(
Arel::Nodes.build_quoted('1 HOUR'),
Arel::Nodes::SqlLiteral.new('INTERVAL')
)
]
)
)
)]
).as('hourly')
# 得られるSQL
# SELECT
# (CAST('2015-08-01' AS TIMESTAMP) + GENERATE_SERIES(0, 23)
# *
# CAST('1 HOUR' AS INTERVAL))
# AS hourly
結局1時間ごとを得るのかよ...
InfixOperation
は第一引数にオペレータをとり、第二引数と第三引数をつなぐみたい
意味は無いが、下記のようにも使える
Arel::Nodes::NamedFunction.new(
'CAST', [
Arel::Nodes::InfixOperation.new(
'AS',
Arel::Nodes.build_quoted('2015-08-01'),
Arel::Nodes::SqlLiteral.new('TIMESTAMP')
)
]
)
# CAST('2015-08-01' AS TIMESTAMP)
先述の通りAs
を使うのが良いと思う
なにより、Arelでは第一引数をオペレータとして扱うので不具合の原因となりそうなので非推奨
まとめ
Arel::Nodesを利用すればArelで用意されていない関数なども使うことができるようになります
また本投稿では全てのモジュールを扱っておりませんので、下記を参考ください
Module: Arel::Nodes