データベース処理

レコードの集合を永続的に保存させ,いつでも取り出せ,さらに加工が できるようにするシステムをデータベース管理システム(DBMS)という。 実用されているDBMSはデータの保存,取り出し以外の豊富な機能を含む 巨大なシステムであるが,簡単なものはRubyプログラムで容易に実装できる。

テキストファイルでの管理

テキストファイルの解析と書き出しはopenメソッドで行なう(既習範囲)。

csvを読んで加工し,csvに書き出す処理は, csvライブラリ を利用する。ただし遅いので(Ruby1.8) 実用性を求めるなら FasterCSVやcsvscanをインストールして使うことになる。

dbm

Unixシステムの多くはdbmという汎用的なデータベースライブラリを 備えている。簡易データベースはこれで十分作成できる。Rubyでは 添付ライブラリの dbm を介して簡単に利用できる。

dbmによるデータベースは Hash と同様 key と value の対の集合をデータとする。ただし,Hashと 違い,keyとvalueがともに文字列(String)でなければならない。

csvで書かれたデータを読み込み,どんどんdbmに追加する例を示す。 csvのレコードは第1フィールドがキーと見なせるとする(重複するものが ないと保証できる)。

dbm-add.rb

#!/usr/bin/env ruby
require 'dbm'                   # dbmクラスを利用
datafile = "./database"         # データベース名

# CSVデータを読んでハッシュにいれておく
data = Hash.new
while csv=gets
  # 第1フィールドをキー,残りをごっそり文字列として値にする
  if /([^,]*),(.*)/ =~ csv
    data[$1] = $2
  end
end

# ndbm形式のデータベースを開く
DBM.open(datafile) do |x|
  # 開いた時点で既にデータがあれば x に入った状態でスタートする
  for key, val in data          # data の key, val ペアを取り出して繰り返す
    x[key] = val
  end
end

走らせるとデータベース名に .dir .pag を付加したファイルができる(BSDの場合は *.db)。 市のデータファイル町のデータファイル 村のデータファイルを順次処理した推移を観察しよう。

./dbm-add.rb yama-city.csv
ls -l data*
-rw-r--r--  1 yuuji  wheel  16384 Apr 27 11:35 database.db
strings database.* | less
(中を確認して q で終了)
./dbm-add.rb yama-town.csv
ls -l data*
strings database.db | less
./dbm-add.rb yama-vil.csv

strings コマンドは何をするものか調べよ。

(n)dbm形式のデータファイルは makedbm -u で テキスト部分を抽出できる。

●Solarisの場合
makedbm -u database
●NetBSDの場合
makedbm -u database.db

作成したデータベースを読み込むプログラムも簡単で, DBM.open を使えばよい。

#!/usr/bin/env ruby
require 'dbm'                   # dbmクラスを利用
datafile = "./database"         # データベースファイル名

# ndbm形式のデータベースを開いて順次レコードを出力
DBM.open(datafile, 0666, DBM::READER) do |x|
  # DBM.open(datafile) だけで開いてもよいが,
  # 読み込みだけのときは第3引数を DBM::READER とする
  for key, val in x             # x の key, val ペアを取り出して繰り返す
    printf("%s -> %s\n", key, val)
  end
end

データ追加,ダンプ,検索の機能を選べるようにした プログラム例を示す。

dbm-ops.rb

#!/usr/bin/env ruby
require 'dbm'                   # dbmクラスを利用
datafile = "./database"         # データベースファイル名

if ARGV[0] == nil then
  STDERR.puts "#{$0} -a [Data.csv]      Add record"
  STDERR.puts "#{$0} -d                 Dump database"
  STDERR.puts "#{$0} -s                 Search on database"
  exit 1
end

case ARGV.shift
when "-a"                       # データの追加登録
  # CSVデータを読んでハッシュにいれておく
  data = Hash.new
  DBM.open(datafile) do |x|     # この処理はあまりよくない(後述)
    while csv=gets
      # 第1フィールドをキー,残りをごっそり文字列として値にする
      if /([^,]*),(.*)/ =~ csv
        #data[$1] = $2
        x[$1] = $2
      end
    end
  end
when "-d"                       # 一覧出力
  DBM.open(datafile, 0666, DBM::READER) do |x|
    for key, val in x
      printf("%s -> %s\n", key, val)
    end
  end
when "-s"                       # 検索
  STDERR.print "検索キー: "
  kwd = STDIN.gets.chomp!       # STDIN無しだと -s というファイルから読む
  reg = Regexp.new(kwd, nil, 'e') # 大文字小文字区別,EUCで検索
  DBM.open(datafile, 0666, DBM::READER) do |x|
    for k in x.keys
      if reg =~ k then          # マッチしたレコードのみ出力
        printf("%s -> %s\n", k, x[k])
      end
    end
  end
end

dbm-ops.rb を実行してみよ。

ただし,このプログラムの追加登録部分は潜在的な問題を含んでいる。

データのキー削除には delete メソッドを用いる。

db.delete(key)

とすると該当するキーと値を削除できる。

PStore

Ruby固有の汎用的なデータ永続化クラスが PStore で,ほぼ全てのオブジェクトがファイルに保存できる。

「商品」と「単価」のkey, valueペアを持つハッシュを永続的に 持ち,追加で増やせるプログラム例を示す。

pstore-basic.rb

#!/usr/bin/env ruby
require 'pstore'
datafile = 'pricedb'

def showall(hash)
  for k, v in hash
    printf("%-20s%4d円\n", k, v)
  end
end

db = PStore.new(datafile)
db.transaction do
  price = db["price-list"] = db.fetch("price-list", Hash.new)
  while true
    STDERR.print "商品=単価  の形式で入れて下さい(C-dで終了): "
    break if (line=gets) == nil
    redo if /([^=]*)=([\d ]*)/ !~ line # マッチしなかったら redo
    price[$1] = $2.to_i
  end
  puts "\n全商品リストです"
  showall(price)
end

プログラム中で指定している "price-list" は, ルート名といい,任意のルート名に対応する値を定義し,それを保存できる。 ひとつのpstoreファイルに複数のルートを持たすことができる。


本日の目次

yuuji@e.koeki-u.ac.jp