Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
27
Help us understand the problem. What is going on with this article?
@takaki_k

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

More than 5 years have passed since last update.

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")

となります。

27
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
takaki_k
事務系のWEBアプリ開発を長年やってきたのですが、一念発起して農業に転向しました。 これからIT化が進んでいく業界ではあるので、今までの知識、技術が(ぼちぼちと)活かされています。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
27
Help us understand the problem. What is going on with this article?