この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
Ruby on RailsからOracleデータベースを利用する際のtipsについて紹介します
設定について
ActiveRecordでOracleを扱うには
activerecord-oracle_enhanced-adapter
というgemを使います
Gemfileには下記のように記述します
gem 'activerecord-oracle_enhanced-adapter'
config/database.ymlのアダプタの項目に下記のように指定します
default: &default
adapter: oracle_enhanced
デフォルトではプライマリキーのシーケンスが10000から始まる(!)ようになっているので、他のDBアダプタと同じように1から始まるように変更します (参照: adapter-settings)
config/initializers/oracle.rb
ActiveSupport.on_load(:active_record) do
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
self.default_sequence_start_value = "1 NOCACHE INCREMENT BY 1"
end
end
文字列の扱いについて
空文字列を検索しづらい件
Rails側でマイグレーション時に string
と指定したカラムは、OracleではVARCHAR2型として定義されます。
Oracleに詳しい方には常識ですが、このVARCHAR2型のカラムには
「長さがゼロの文字列(空文字列)はNULLとみなされる」
という他のRDBMSと違う大きな落とし穴があります。
そのため、WHERE条件にVARCHAR2型のカラムを含める場合、カラムの値が ” の行を検索したいとき
SELECT * FROM tbl1 WHERE col1 = '';
こういうSQLを投げると、 WHERE col1 = NULL
と解釈されてヒットしません。
Rails側のコードでいうと
str = ""
Tbl.where(col: str)
これがだめで、 str
が ” ならば必ず nil に置き換える必要があります。
pry(main)> str = ""
=> ""
pry(main)> Customer.where(name: str).count
(0.6ms) SELECT COUNT(*) FROM "CUSTOMERS" WHERE "CUSTOMERS"."NAME" = :a1 [["name", ""]]
=> 0
pry(main)> Customer.where(name: str.presence || nil).count
(0.7ms) SELECT COUNT(*) FROM "CUSTOMERS" WHERE "CUSTOMERS"."NAME" IS NULL
=> 1
カラムがVARCHAR2の場合は ” を nil に置き換えてクエリを投げる、くらいのことはアダプタ側でやってほしいですね。
CLOB型のWHERE検索が難しい
Rails側でマイグレーション時に text
と指定したカラムは、OracleではCLOB型として定義されます。
SQLレベルではCLOB型の値とVARCHAR2型の値には互換性がなく、暗黙のキャストが行われないという他のRDBMSとは異なる不便な特徴があります。
シングルクオートを使った文字列リテラルはOracleのSQL構文においてVARCHAR2型の値となるため、CLOB型のカラムをWHERE条件に指定しようとすると、SQLエラーになります。
pry(main)> Customer.where(memo: 'hoge').to_a
Customer Load (2.0ms) SELECT "CUSTOMERS".* FROM "CUSTOMERS" WHERE "CUSTOMERS"."MEMO" = :a1 [["memo", #<OCI8::CLOB:0x00558008772008>]]
ActiveRecord::StatementInvalid: OCIError: ORA-00932: データ型が一致しません: -が予想されましたがCLOBです。: SELECT "CUSTOMERS".* FROM "CUSTOMERS" WHERE "CUSTOMERS"."MEMO" = :a1
試行錯誤の末、こういう式で通るようになりましたが、当然ながらインデックスは効きません。
pry(main)> Customer.where('DBMS_LOB.SUBSTR(memo,1000,1) = ?', 'hoge').to_a
Customer Load (5.5ms) SELECT "CUSTOMERS".* FROM "CUSTOMERS" WHERE (dbms_lob.substr(memo, 1000, 1) = 'hoge')
=> [#<Customer:0x0055800a7de210 (後略
まとめ
RailsからOracleデータベースを利用する場合、文字列の扱いなど他のRDBMSにはない特殊な特性を把握して、注意しながらコーディングしていく必要があります。
「こう書けば普通に動くだろう」という予断を置かず、実際にこまめに実験して想定通りの結果が得られることを確認しながら進めていきましょう。
次回はOracle Textを使った日本語全文検索など、高度なトピックについて触れていく予定です。