なんとな~くしあわせ?の日記

「そしてそれゆえ、知識そのものが力である」 (Nam et ipsa scientia potestas est.) 〜 フランシス・ベーコン

SQLもプログラミング言語…っぽい4

このシリーズも4回目になる。お題は以下の通り

テーブルの正規化

正規化とはなんぞや?

ここが分かりやすかった → データベースの正規化(正規形)とはなんぞや

以下、上のリンクに従って概要をまとめてみる

非正規形

わかりやすく言うとExcelで左のセルを縦に結合したとき、右に複数行のデータがあればそれは非正規形であると言える。データベースに配列型をぶっこもうとしている状態、アカンのである。

食品名 栄養成分 単位
精白米
炭水化物 168 kcal
エネルギー 37.1 g
ビタミン C 1 μg
D 1 μg
E 1 μg

上の例だと、「精白米」という1レコードに対して「炭水化物、エネルギー、ビタミン」という3レコードがくっついている。

この考えをそのままデータベースに適用しようとすると、配列型が用意されてないDBだとできない*1。それで、VARCHAR型に無理やりカンマ区切りでデータを入れたりするやらかし先生がいるかもしれない。「炭水化物,エネルギー,ビタミン」みたいな感じで…

実は、これはSQLの有名なアンチパターンで、「Jaywalking」でググるといろいろ出てくる。

www.slideshare.net

上のスライドには種本がある、自分はまだ読めていない

SQLアンチパターン

SQLアンチパターン


第1正規形

先ほどのExcelの結合部分を右の行数に合わせて分割する。繰り返し項目が増えるが、これでレコードがそれぞれ1行で表せる。

食品名 栄養成分 単位
精白米 炭水化物 168 kcal
精白米 エネルギー 37.1 g
精白米 ビタミンC 1 μg
精白米 ビタミンD 1 μg
精白米 ビタミンE 1 μg

第2正規形・第3正規形

この2つはそんなに変わらない気がする。表を独立する部分で切り出して行けば自ずとこれになる*2。自分が作った例では表が簡単すぎて分割できない…他のサイトを見てみてください。

外部結合の設定

SQLは上の要領で分割して粗結合にしたテーブルを外部結合して見たい情報を揃えるのが一般的な使い方だ。

以前取り上げた例をもとにやってみよう
SQLもプログラミング言語…っぽい - なんとな~くしあわせ?の日記

LEFT JOIN


Parentテーブル

主キー 整理番号
1 12345 山田 太郎
2 12346 山田 花子
3 12346 山田 花子
4 12347 山田 嘘子
5 12348 山田 良子


Childテーブル

整理番号 住所 郵便番号
12345 どこかの台 1丁目 131-0202
12346 すすきが原 3丁目 251-0003
12346 ニセコ 201-0003

Parentテーブルの整理番号とChildテーブルの整理番号が共通してるので、Parentテーブルを基本として左外部結合してみる

CREATE TABLE parent(`主キー` text, `整理番号` int, `姓` text, `名` text);

INSERT INTO parent(`主キー`, `整理番号`, `姓`, `名`)
VALUES (00001, 12345, "山田", "太郎")
, (00002, 12346, "山田", "花子") 
, (00003, 12346, "山田", "花子") 
, (00004, 12347, "山田", "嘘子") 
, (00005, 12348, "山田", "良子");

CREATE TABLE child(`整理番号` int, `住所` text, `郵便番号` text);

INSERT INTO child(`整理番号`, `住所`, `郵便番号`)
VALUES (12345, "どこかの台 1丁目", "131-0202")
, (12346, "すすきが原 3丁目", "251-0003") 
, (12346, "ニセコ", "201-0003");

このように、左に指定したテーブルが中心となる。そのため右に結合したテーブルにNULLが入ることもある。

主キー 整理番号 整理番号 住所 郵便番号
1 12345 山田 太郎 12345 どこかの台 1丁目 131-0202
2 12346 山田 花子 12346 すすきが原 3丁目 251-0003
3 12346 山田 花子 12346 すすきが原 3丁目 251-0003
2 12346 山田 花子 12346 ニセコ 201-0003
3 12346 山田 花子 12346 ニセコ 201-0003
4 12347 山田 嘘子 (null) (null) (null)
5 12348 山田 良子 (null) (null) (null)

右に配置されるレコードが複数行ありえる場合、レコードが増えてしまう。それを防ぐためには、以下のようにレコードを一意に絞りこめるようにする。

LEFT JOIN child
ON parent.`整理番号` = child.`整理番号`
AND child.`郵便番号` = '251-0003'

書籍の紹介

とまあここまでは実は応用情報技術者試験の範囲のようだ。

以下の本のSQLのところを見るともっと網羅的な解説が載っている。

ニュースペックテキスト 応用情報技術者 平成29・30年 (情報処理技術者試験)

ニュースペックテキスト 応用情報技術者 平成29・30年 (情報処理技術者試験)

履歴管理

みんなやってることなので、Qiitaに記事がある

実際的な設計

これ読んどけば十分な気がする

たぶん大体のRDBの人々は1番目のテーブルにバージョン番号を持たせる方法を使っているのではないだろうか。2番目の履歴テーブルをもう一つ作る方法は結構めんどくさそう。

しかし、Stackoverflowの同様のスレッドを見てみると2番目を薦める人が多かった(そしてベストアンサーはRDBをそういうことに使うな!という解答。まあ履歴番号だけ持たせて、実装はGitに任せるとかのほうが正確なシステムにはよいのかもしれない。)

stackoverflow.com


*1:Postgresql先生にはあるようだ ぱせらんメモ - PostgreSQLで配列型のカラムを使ってみる

*2:コッド博士はこれをちゃんと定義して説明したから偉い、想像するだけなら凡人でもできる

AOJ - ALDS1_7_A, B, C (木構造) を解いてみた

勝手に解いてろとか言わないで…

ALDS1_7_A, B, Cは、木構造(根付き木、二分木)です

根付き木 | アルゴリズムとデータ構造 | Aizu Online Judge
二分木 | アルゴリズムとデータ構造 | Aizu Online Judge
木の巡回 | アルゴリズムとデータ構造 | Aizu Online Judge
木の復元 | アルゴリズムとデータ構造 | Aizu Online Judge

木構造を使うときの基本

まあ、木構造単体でプログラミングコンテストの問題が出されることはほとんど無さそうなのですが…*1

  • とりあえず、構造体 or クラスで以下のような型を作る
  • 木構造の1要素を1つのNodeというクラスで表現します。中には親要素のid、左と右の要素のidを持ちます。
  • Rubyの場合はStructを使うと楽にそういうのが作れる、Generics無しにそれを配列型にできるし
Node = Struct.new(:parent, :left, :right)
nodes = Array.new(N) { Node.new }

ALDS1_7_A - 根付き木

1つの節(ノード)に複数の子がくっつく場合の構造。

自分の回答

用意した関数を紹介しておく

get_depth ( id, nodes )

その接点の深さを求める

get_children ( id, nodes )

その接点の直下の子ノードを求める

ALDS1_7_B - 二分木

1つの節(ノード)に2つの子しかつかない場合の構造。

自分の回答

用意した関数を紹介しておく

get_depth ( id, nodes )

その接点の深さを求める

get_height ( id, nodes )

その接点の高さを求める、深さと逆のため再帰的にノードを求める必要がある(Finding height in Binary Search Tree - Stack Overflow)

get_sibling ( id, nodes )

その接点と同じ階層にあるノードを求める、二分木の場合隣接するものはあるかないか2択になる

ALDS1_7_C - 木の巡回

木の巡回には3種類あるという話。

  • 根節点、左部分木、右部分木の順で節点の番号を出力する。これを木の先行順巡回 (preorder tree walk) と呼びます。
  • 左部分木、根節点、右部分木の順で節点の番号を出力する。これを木の中間順巡回 (inorder tree walk) と呼びます。
  • 左部分木、右部分木、根節点の順で節点の番号を出力する。これを木の後行順巡回 (postorder tree walk) と呼びます。

自分の回答

用意した関数を紹介しておく

pre_parse ( root, nodes )

根節点、左部分木、右部分木の順で節点の番号を出力する

in_parse ( root, nodes )

左部分木、根節点、右部分木の順で節点の番号を出力する

post_parse ( root, nodes )

左部分木、右部分木、根節点の順で節点の番号を出力する

使用例?

単純な木構造って、実務でも競プロでも使うことが稀な気がする。すごい単純なデータが問題で、なおかつ階層がキーになる場合に使えばいいのかな?いい問題があれば追記します。

追記: 木構造の問題です

A Rational Sequence 2 – Kattis, Kattis

*1:木構造よりも、それの発展系のグラフアルゴリズムの出題が多そう

MyBatisエラー別対応とか

Wikiの方に書いた記事によくアクセスがあるようなので、はてなに移しておく。

MyBatisのエラー別対応

エラーメッセージ:"Mapped Statements collection does not contain value for クラス名"

原因

  • ディレクトリ構成が変
  • Mapperファイルに対応するソースファイルを用意していない、またはその逆

ここのサイトに詳しいトラブルシュートが書いてある

ameblo.jp

引用

たぶん設定で変えられると思うのですが、Mavenではリソースの配置場所が最初からあらかじめ決められています。javaのソースファイルはsrc/main/java配下に、それ以外のリソースはsrc/main/resources配下に。他にもいろいろ決まっているので、気になる方はここ(Introduction to the Standard Directory Layout)を参照してください。つまり、classファイル出力先でjavaxmlを一緒にしたい場合、src/main/java配下にHogeMapper.javaを作成したら、src/main/resources配下にHogeMapper.xmlを置く必要があったわけでした。

  • 上記の場合は、Mavenで決められてるソースファイルやリソースファイルのディレクトリ仕様に従っていないから出るエラーでした。
  • あとはおそらく、Mapperファイルに対応するソースファイルを用意していない、またはその逆をやらかした時に出るエラー

このディレクトリ仕様に関する話はどうやらMyBatisのドキュメントの方には書いてないようです。まあMyBatisのデベロッパーたちもまさかそんな間違いをされるとは思ってなかったのかも(そうしないでも動くことは動く)

エラーメッセージ:"Mapped Statements collection already contains value for クラス名"

原因

  • Mapperファイルの二重読み込み

上とは逆の話

stackoverflow.com

MyBatisは実行時にクラスパス上にあるすべてのXMLファイル(Mapper)を見ようとする。eclipseなどのIDEを使っている場合、生成された余計なクラスファイルを見に行った結果、2重にXMLを読み込んでいることがある。

XMLファイルの指定は様々だが、MyBatis+Springならば例えばこう

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="configLocation" value="/WEB-INF/config/mybatis-config.xml" />
    <property name="mapperLocations" value="/WEB-INF/classes/your/package/name/*.xml" />
</bean>

Mapper用のXMLファイルがMavensrc/main/resources以下に配置されていたものとする。その場合、Mavenビルドのシーケンス上 compileフェイズで target/classes 以下にクラスファイルとXMLファイルが吐かれる。同様にして、{生成されたアプリ名}/WEB-INF/classes/your/package/name/*.xml上にもマッパーファイルが吐きだされる。eclipseのJettyプラグインなどでこれを実行すると、本来は後者のXMLファイルのみを読み込むことを想定しているのだが、前者のXMLも後から読み込まれるのでこのエラーが出る。どちらかというとeclipseが余計なおせっかいをしている印象だ。とりあえずclassesディレクトリを削除してアプリを動かすと問題なく動くようになる。

まとめ

  • Mavenのビルドファイルは開発の本筋とは関係ないけど、知っておくといろいろ便利なのでXML、書こう!