QA@IT

RDBMS でユーザーが自由に設問を用意できるフォームアプリ等での適切なschema

4890 PV

興味が出たので質問します。Google Drive (docs) 等にある Form 等では自由に設問が用意できますが、以下のようなフォームアプリの時適切なDBの構造はどうなのか気になります。

  • フォームを自由に作成できる
  • フォームにはほぼ無制限に設問を作成でき、チェックボックスやテキストフォーム、日付など複数のフォーマットがある
  • 設問毎に必須かどうかも指定できる

SQLで検索できる必要はないときは、回答・フォームの設問等はtextでJSONとかで持っておくのが良いんでしょうか。

(そもそもRDBMSが向いてなさそうという事も認識しています)

回答

こんにちは。RDBMS派の方から来ました。

ある程度仕様が固まっているのであれば、Formsテーブル・FormItemsテーブル・FormItemOptionsテーブル・Answersテーブル・AnswerItemsテーブルなどでおおむね実装できそうな気がします(というか、実装したことがあります)。

チェックボックスで複数回答可の場合、その回答をどう保持するか(チェックボックスの場合、複数の選択肢の項目を1つの設問への回答として保持する必要がある)が気になりますが、PostgreSQLなら配列型を使うといった手もあります(駄目ならAnswerItemOptionsテーブルなどを使います)。

DBのレコードからHTMLのフォームを生成するのは多少骨が折れますが(JSを駆使して動的生成されるフォームにするなら死ぬほど骨が折れるかもしれませんが)、まあ何とかならなくはないレベルかと思います。

あと、一般的な話としては、フォームを作り、回答を募集開始した後でフォームを変更したい(選択肢を増やしたり減らしたり変えたりしたい、ラジオボタンを自由入力にしたい等)、という要望が出ます。これは「できません」「やるべきじゃありません(回答済みのデータが壊れがちなので)」と断っておいても必ず要望が出ます。ので、その対応を考える必要があります(場合によっては回答済みのデータを変換できるようにします。もちろん限界があるのでどこまでやるかは仕様も含めて応相談になるはずです)。

編集 履歴 (1)
  • 苦行そう…! ありがとうございます。 -

過去にそういうフォームを設計・実装したことがあります。結論から言うと、失敗しました。なので、反面教師としての回答ということで。

バックエンドには MySQL を使いました。スキーマはうろ覚えですが、フォームの部品ごとに一行ずつ挿入していくように作った覚えがあります。 form_id 的なものをキーに持たせていたと思います。

Perl で書かれたシステムの一部だったため、フォームビルダーに CGI.pm を利用しようと考えました (これがまずかった) CGI.pm は Ruby の cgi ライブラリみたいなものです。例えばこんなコードでラジオボタンの HTML を生成できます。

print radio_group(-name=>'group_name',
                          -values=>['eenie','meenie','minie'],
                          -default=>'meenie',
                          -linebreak=>'true',
        -labels=>\%labels,
        -attributes=>\%attributes);

この入力パラメータ (連想配列) を Data::Dumper でシリアライズした文字列を TEXT 型のカラムに保存しました。 Data::Dumper というのは Perl の配列やら連想配列やらを eval できる文字列にシリアライズするもので要するに Perl 専用の JSON みたいなものです。ここが極めて ugly で、 SELECT したときの可読性はゼロですし、あとから細かくフォーム部品のパラメータを修正するのも骨が折れました。また、いわゆるマスタ値 (フォームラベルのデフォルト文言とか) をソースコードにリテラルで書いていたため、ビルダー部分のソースコードもたいへんに ugly でした。

ユーザー入力値をどのように保存していたか、テーブルのリレーション設計的なものは覚えてないのですが、データそのものはやはり Data::Dumper でシリアライズしたものを TEXT 型のカラムに保存した気がします。当然 SQL では検索できません (実装前に三回くらいは検索の必要性について確認したおぼえがあります) 幸か不幸かさほどデータの多いシステムではなかったので、 SQL による検索は結局必要になりませんでしたが、単に集計するだけでも苦労しました。

それなりに苦労してでっちあげたシステムでしたが、評判は芳しくなかったと記憶しています。まず、使いづらいんですね、フォームビルダーの利用者にとって。なんか設計か実装のミスで、同じフォーム部品を複数設置したときに微妙にバグったりして。メンテナンス性は最悪の部類でした。たぶん僕があれの後任を仰せつかったとしたら設計者を藁人形で呪うでしょうね。いまだからこそ言えますが、他にいくらかでもマシなやり方がないか立ち止まって考えなおすべきでした。まぁ、後年マシなやり方が思いついたわけではないんですが。

以上のことからまとめると、

  • 可読性の低いデータ保存形式を採用するな (せめて pretty print して保存しろ)
  • 既存ライブラリの機能を無理に使うな (パーツごとに部分テンプレートや HTML 断片を保持してふつうのテンプレートエンジンに埋め込むほうが潰しが効く)
  • (可能であれば) 利用者にヒアリングをしろ (必要なのはフォームビルダーではなくて私が速くフォームをでっち上げるためのジェネレーターなのではないですか?)

この回答が何かの参考になれば幸いです。

編集 履歴 (0)
  • ありがとうございます。もし実装する際は、ugly にならないように注意します。 -

BLOB型やTEXT型のカラムで持たせてJSONあるいはYAMLで保存するのがパターンですかね。使い分けのポイントとしては、データとプログラミング言語の結びつきを疎にしたい場合はJSONで、Rubyなど特定の言語でより使いやすくしたい場合にはYAMLという感じで。

たとえば、Rails 3.2のActiveRecord::StoreはデフォルトYAMLで保存するので、取り出したときにActiveSupport::HashWithIndifferentAccessなどのクラス拡張も一緒に復元されるのが結構便利だったりします。

http://api.rubyonrails.org/classes/ActiveRecord/Store.html

なぜJSON/Marshal/XMLでなくYAMLなのか?についてはAaron Pattersonが

http://www.youtube.com/watch?v=kWOAHIpmLAI#t=16m

で歴史的にActiveRecordとYAMLの結びつきが強かったから、と説明しています。今はload/dumpを実装したコーダーを渡せば好きにできるっぽいですが、フォーマット選択の参考になると思います。

最後に、この形式で入れるデータは「本当に、本当に、本当に、将来的にもSQLで検索できる必要はないのか?」と3回自問してから決めましょう。もしあとで検索したくなったら超面倒なので。。。

編集 履歴 (1)
  • ありがとうございます、ActiveRecord::Store かー、そんなものもあったなー -
  • YAMLはsyckが仕様違反してる部分があるという地雷が埋まっているので、今からやる場合は psych を明示的に指定するようにしましょう http://bugs.ruby-lang.org/issues/6910 -
  • おぉ確かに。Ruby 1.9.2以降ならデフォルトpsychになってると思うのですが、たまにYAML::ENGINE.yamler = 'syck'してる極悪ライブラリもあったりするので、気をつけたほうがいいポイントですね。ありがとうございます。 -

アンケートフォームでそのような要求があったんですが、その時はSQL ServerのXML型を使いました。
詳細は記憶のかなたですが、XML型の値を直接Selectしたりもできます。
当然ながらXML DB的な使い方はSQL Server本来の使い方ではないので、小規模なものに限られてしまいますが、参考情報ということで。。。

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。