ホーム DoRuby RailsでOracle~ 導入と文字列の扱いについて
RailsでOracle~ 導入と文字列の扱いについて
 

RailsでOracle~ 導入と文字列の扱いについて

この記事はアピリッツの技術ブログ「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を使った日本語全文検索など、高度なトピックについて触れていく予定です。

記事を共有

最近人気な記事