LoginSignup
25
27

More than 5 years have passed since last update.

UNIONを使いたいときはVIEWを作った方が手っ取り早い

Last updated at Posted at 2015-05-21

ActiveRecordを使ってUNIONを実装しようとしていたのですが、どんどん複雑になっていきます。
それなら、VIEWを作った方が手っ取り早いです。

migrationファイルなど、SQLを直接記述するようなこともありますが、環境を意識しながら実装していくのであれば問題ないかと。

なお、Data Baseが
* MySQL
* PostgreSQL (native and pure ruby)
* SQL Server
であれば、rails_sql_viewsというgemもあります。
ですが、ここでは他のData Baseでも対応できるように、ベタなサンプルで実装しています。

実装環境

OS
CentOS 6.5
Ruby
2.1.5
Rails
4.2.0
DB
MySQL

サンプル用のテーブル作成

今回、サンプル用に作成するテーブルです。
わざわざ二つのテーブルに分ける必要は無いのですが、サンプル用ということで勘弁してください。

ユーザテーブルのmigrationファイルの作成
bundle exec rails g model user name:string email:string

ゲストテーブルのmigrationファイルの作成
bundle exec rails g model guest name:string email:string

VIEWの作成

サンプルのテーブルをUNION ALLして、一つのVIEWとします。
generateでは作成できないので、手動で実装します。
今回は、usersguestsを統合して、view_peopleというVIEWを作成します。
(ファイル名のxxxxxxxxxxxxxxの部分は適宜、年月日時分秒を指定してください。)

xxxxxxxxxxxxxx_create_view_people.rb
class CreateViewPeople < ActiveRecord::Migration
  DB_NAME = "view_people"   # VIEWの名称

  def up
    execute create_view_sql
  end

  def down
    execute drop_view_sql
  end

  def create_view_sql
    db_adapter = ActiveRecord::Base.connection_config[:adapter]

    case db_adapter
    when 'mysql2' then
      create_mysql_view_sql
    else
      raise Exception, "Not Support Data Base [#{db_adapter}]"
    end

  end

  def drop_view_sql
    "DROP VIEW #{DB_NAME}"
  end

  def create_mysql_view_sql
    "
      create or replace view #{DB_NAME}
      as
        select
          'user'  as tbl_type,
          name              ,
          email             ,
          created_at        ,
          updated_at
        from
          users
        union all
        select
          'guest' as tbl_type,
          name              ,
          email             ,
          created_at        ,
          updated_at
        from
          guests
      ;
    "
  end

end

さて、単純にdef upCREATE VIEWを書いても良いのですが、直接SQLを書くことを考慮して、Data Base別に実装できるようにしています。

ActiveRecord::Base.connection_config[:adapter]は、config/database.ymladapterを取得します。
これで実装相手のData Baseを判断して、それに合ったCREATE VIEWを生成します。

def downは、Data Base毎に書き方が変わることはないかと思い、そのまま書いています。
もし、違う場合があれば、def up同様、切り分けてください。

最後に、rake db:migrationをして、エラーが出なければOKです。

MODELの作成

上記でVIEWはできましたが、VIEWを参照するために、モデルを作成します。

このMODELは普通のテーブルとなんら変わりません。

app/models/view_person.rb
class ViewPerson < ActiveRecord::Base
end

これだけです。
(必要に応じて、メソッド等は追加してください。)

ここで注意が必要です。

こんなことをやってみました。

obj = ViewPerson.new
obj.tbl_type='guest'
obj.name='hoge'
obj.email='hoge@hoge.com'
obj.save

インスタンスから値の代入までは問題なく実行できるのですが、saveは失敗しました。
MySQLからERRORが戻ってきます。
つまり、UNIONを使用したVIEWでは保存処理はできません。

ですが、MySQLからERRORが戻ってきたということは、保存できるようなVIEWであれば、saveもできそうです。(未検証)

VIEWを使わなかったら

ActiveRecordでUNIONは以下のように書きます。

UNION

ViewPerson.from("#{User.select("'user' as tbl_type, name, email").union_all(Guest.select("'guest' as tbl_type, name, email")).to_sql} view_people")

もしくは

ViewPerson.from("#{Arel::Nodes::Union.new(User.select("'user' as tbl_type, name, email").ast, Guest.select("'guest' as tbl_type, name, email").ast).to_sql} view_people")

UNION ALL

ViewPerson.from("#{Arel::Nodes::UnionAll.new(User.select("'user' as tbl_type, name, email").ast, Guest.select("'guest' as tbl_type, name, email").ast).to_sql} view_people")

となります。

25
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
27