要約:リレーショナル・データベースが扱いづらい問題について、RDB初心者が思案を巡らしてみた記事
この記事は書いている内に、問題解決の糸口が見えてきたけど、問題だと思っているところをはっきりさせるために、そのまま書いた記事です。
以下に、その思考ログを書いておきます。
RDBが使いづらい理由
最近、MySQLやSQLiteなどのリレーショナル・データベースを使い始めたのですが、XMLをどうにも使いづらいという感は抜けません。そんなこともあって先日は擬似XMLデータベースなんてへんてこなものを作ったりしたのですが、気がついてみればその理由をあんまり書いてなかったので、使いづらさに慣れちゃう前に理由を整理してみました。
スキーマを事前に定義しておかないといけない
開発中にはデータベースのデータ構造が変化すると言うことは良くあります。そのたびに、スキーマ定義をカリカリ書き換えているのは面倒ですから、データベースには速度だけでなく、柔軟性・拡張性が求められることになります(これはリレーショナルデータベースとXMLデータベースとの対比で良く言われます)
データ型として配列が使えない
簡単に言えば、一つのレコードに複数の値を設定できないということです。ブログのタイトルのようにキーと値が1:1で対応している場合は問題ないのですが、例えばこの記事のカテゴリのように、あるキーに複数の値が対応する(1:n)ということはよくあることです。しかし、どうもマニュアルを見る限りは、MySQLに複数の値を格納できる型、つまりは配列型はないようなのです。
この場合、一般的にはシリアル化(serialize関数)して文字列として処理→DBから取り出した後に復元(unserialize関数)(PC用語で言うと圧縮、解凍みたいなものですね、サイズは変わりませんが)という方法が採られているようですが、一手間かけさせられているというか、言語側の仕様に依存しているようでどうにも納得いきません。
この項、シリアライズが一般的と書きましたが、いくつかご指摘をいただきまして、標準的には第一正規化(つまり別テーブル化)するのが一般的だそうです。まぁ、どちらにしても一手間かけなければいけないことに代わりはないようなのですが、勉強になりました。
ちなみに、この手順をコードにしてみるとこんな感じ(例:商品の名前・価格・カテゴリについて書いてあるデータベース)
/** データを挿入するとき **/
$category = array(
"音楽再生機器" => "MP3プレイヤー",
"iPod" => "本体",
"スマートフォン" => array("Apple","ソフトバンクモバイル")
);
$vars = array(":id"=> 10, ":name"=> "iPhone", ":price"=> 200000, ":category"=> serialize($category));
$sql = "INSERT INTO 'mytable' (id, name, price) VALUES (:id, :name, :price)";
$request = $db->prepare($sql);
$request->execute($vars);
// (中略)
/** データを取り出すとき **/
// SELECT文の発行とかはすっとばします
$names = array("id", "name", "price", "category");
while ($row = $request->fetch(PDO::FETCH_ASSOC)){
foreach ($names as $name){
$v = $row[$name];
// gettype関数先生は全部stringと仰るので
// 泥臭く文字列としてシリアライズされているか判定
$results[$i][$name] = (preg_match("/^a:[0-9]+:{/", $v)) ? unserialize($v) : $v;
}
$i++;
}
/*
$results = array(
[0] => array(
"id"=>10,
"name"=>"iPhone",
"price"=> 200000,
"category"=>array(
"音楽再生機器" => "MP3プレイヤー",
"iPod" => "本体",
"スマートフォン" => array("Apple","ソフトバンクモバイル")
)
),
[1] => array(
...
)
);
*/
ちなみに、SET型はユーザ定義型(あらかじめ入る内容を定義しておかないと行けない)なので、配列的に使うには無理がありますし、join関数を用いた単純結合は多次元配列を処理できません。
プログラミング言語との連携が弱い
データベースはプログラムとセットにして使うものなのに、その連携が弱いんじゃないかなと思うのです。
例えば、先の例で商品データベースを使いましたよね。再掲すると…
$category = array(**省略**);
$vars = array(":id"=> 10, ":name"=> "iPhone", ":price"=> 200000, ":category"=> serialize($category));
$sql = "INSERT INTO 'mytable' (id, name, price) VALUES (:id, :name, :price)";
$request = $db->prepare($sql);
$request->execute($vars);
これ、個人的にはこう書きたいんですね(連想配列を投げる)
$vars = array("id"=> 10, "name" => "iPod nano", "price" => 200000, "category"=>$category);
$request = $db->insert("mytable", $vars);
今回は後者を前者に置換するラッパ関数を書けば良い話ですが、さきほどの配列型の問題と絡めて、こういうのがデフォルトになってくれるといいなと。
まぁ、あと細かいことを挙げれば
- データの挿入(INSERT)と更新(UPDATE)でクエリの書式が違う
- SQLインジェクションが構造的に発生する
- データがバイナリファイルである(これはちょっとイチャモンぽいですが)
とありますが、冗長になるので深くはつっこまないことにします。
O/Rマッパーがいいらしい?
検索していると、連携問題はどうやらオブジェクト関係マッパー(O/Rマッパー)と呼ばれるもので解決できそうな感じです。
このO/Rマッパーというのは、要するに人間がデータベースを触りやすくなるように、プログラミング言語のお作法で違和感なくデータベースがいじれるようにするような仕組みのようです。PHPのフレームワークの一つであるCakePHPでは、ActiveRecordという考え方(デザイン・パターンとか言うらしい)を基にしたO/Rマッパーが実装されている模様。
配列型問題はどうだろう…定義からして解決できるぽい感じはするのですが、具体的な記述が見つけられません。あと、最初にデータベース定義が必要なところはどうも解決してないっぽいですね。むー
O/Rマッパーは問題を解決する?
じゃあ、O/Rマッパーを使えばいいのかというとそれもちょっと疑問なんですよね。僕が抱えてる問題はちょっとは解決するんですが、別に思うこともありまして…。で、これには2つの理由があります。
- 初心者は結局、SQLを学ばないといけない
- O/Rマッパは対処療法である
まず、「じゃあ初心者ってO/Rマッパ使うのか」という疑問です。PHPの入門書でも、だいたいPEAR::MDB2の使い方を解説するか、PDOの使い方を解説するくらいが現状なわけで、結局はSQLの書き方やお作法を学ぶことになるでしょう。
Wikipediaの「O/Rマッパー」の項にはオブジェクト関係マッピングは、オブジェクト指向と関係モデルのインピーダンス・ミスマッチ問題の対症療法にすぎないとも言われている
と書かれていますが、これはその通りで、上記の問題が起こるのもこの問題があるからでしょう。
つまり、O/Rマッパーはプログラム言語とSQL言語の翻訳者でしかないわけで(断言してますけど、自信はないです)、SQL自身が関係モデルを取り、SQL言語を話している限りは、その問題の根本が解決したとは言えないのです。とはいえ、オブジェクトデータベースやXMLデータベースは、PerlやPHPなどプログラム初心者が手を出しやすい軽量言語では実質使えない状況です。
初心者にとって、プログラム言語とSQL言語のバイリンガルにならざるを得ない状況があって、それがプログラミングの壁を厚くしているような感じがします。まぁ、現状はO/Rマッパー(を搭載したフレームワーク)が最適解ぽいので、この問題は、「現状は初心者こそCakePHPを使うべき」が落としどころになりそうです。しかし、入門書にフレームワークの記述は難しいだろうなぁ。
言及されたURL
以下で言及されました(ありがとうございます)。
ORマッパーとか | 眠る開発屋blog
SQLのコストについて。確かにSQL学習上のコストはそれほど高く付くわけではないのですしこの先数年も流通している知識だとは思うのですが、それが本来必要なコストなのかどうかということをSQL初学者として書いておきたかったのでした(できるならデータベースのお作法を気にせずに=透過的に、扱えるようになるべきでしょうし)。
- キーワード:





読者のコメント
3件