書籍テーブル
主キー
出版社ID,ISBN10
出版社ID | 出版社名 | 著者ID | 著者名 | ISBN10 | 書籍名 | 価格 |
---|---|---|---|---|---|---|
100 | A社 | 001A | 鈴木さん | 9987654321 | 入門C言語 | 111 |
200 | B社 | 001B | 加藤さん | 1111111111 | 入門JAVA | 222 |
200 | B社 | 001B | 加藤さん | 2222222222 | 入門Scala | 333 |
300 | C社 | 001C | 高橋さん | 1111111111 | 入門Ruby | 444 |
100 | A社 | 002A | 鈴木さん | 1234567890 | 入門HTML | 555 |
第一正規形create文
CREATE TABLE BOOK_N1 ( COMPANY_ID INTEGER NOT NULL, COMPANY_NAME VARCHAR(255) NOT NULL, AUTHOR_ID CHAR(10) NOT NULL, AUTHOR_NAME VARCHAR(255) NOT NULL, ISBN10 BIGINT NOT NULL, BOOK_NAME VARCHAR(255) NOT NULL, BOOK_PRICE NUMERIC, PRIMARY KEY(COMPANY_ID, ISBN10) );
insert文
INSERT INTO BOOK VALUES (100, 'A社', '001A', '鈴木さん', 9987654321, '入門C言語', 111); INSERT INTO BOOK VALUES (200, 'B社', '001B', '加藤さん', 1111111111, '入門JAVA', 222); INSERT INTO BOOK VALUES (200, 'B社', '001B', '加藤さん', 2222222222, '入門Scala', 333); INSERT INTO BOOK VALUES (300, 'C社', '001C', '高橋さん', 1111111111, '入門Ruby', 444); INSERT INTO BOOK VALUES (100, 'A社', '002A', '鈴木さん', 1234567890, '入門HTML', 555);
第一正規形
テーブルが第一正規形を満たす条件は以下の通りです。
【1つのセルの中には1つの値しか取らない】
この条件を満たしているセルの値のことをスカラ値と呼ぶ
ということで、この時点で書籍テーブルは第一正規形化されていることが確認できます。
なぜ1つのセルの中には1つの値しか取らないといけないのかというと、
それは関数従属性という正規形を理解するための概念と結びついているからみたいです。
関数:Y = f(X)
このとき、YはXに従属すると表現します。
この考え方をテーブルに当てはめると、X列の値を決めれば、Y列の値が1つに決まるという意味になります。
つまり、主キーの列の値から特定の列の値が1つに決められることがポイント。
よって先の書籍テーブルは関数従属性を満たしているといえる。
第二正規形
第一正規形の書籍テーブルでは、運用していく上でいくつかの問題が考えられます。
まず、出版社名の変更があった時、【出版社ID, 出版社名】が【100, A社】の他に【100, AA社】も入力ができてしまいます。
また、主キーの一部にISBN10を使用してしまっているため、ISBN10がわからないと出版社を登録することができないことがあります。
これらの問題を引き起こさないために、第二正規化を行っていきます。
ここでも関数従属性の概念がポイントになっています。
第一正規化が済んだ先ほどの書籍テーブルは、関数従属が完全ではないので、第二正規形ではありません。
どういうことかというと、
このテーブルの主キーは【出版社ID、ISBN10】であり、その他の列はこのキーに従属するのですが、よく見ると、出版社名だけは主キーの一部である出版社IDに従属しています。
このような関係は部分関数従属と呼ばれています。
これに対して主キーを構成するすべての列【出版社ID、ISBN10】に従属する関係は完全関数従属と呼ばれます。
第二正規形のテーブルは、部分関数従属がすべて排除され、完全関数従属のみで構成されているものをいいます。
以下に第二正規化された書籍テーブルと、新しく作った出版社テーブルを載せます。
どちらのテーブルも完全関数従属のみで構成されていることが見てわかると思います。
書籍テーブル
主キー
出版社ID,ISBN10
外部キー
出版社ID
出版社ID | 著者ID | 著者名 | ISBN10 | 書籍名 | 価格 |
---|---|---|---|---|---|
100 | 001A | 鈴木さん | 9987654321 | 入門C言語 | 111 |
200 | 001B | 加藤さん | 1111111111 | 入門JAVA | 222 |
200 | 001B | 加藤さん | 2222222222 | 入門Scala | 333 |
300 | 001C | 高橋さん | 1111111111 | 入門Ruby | 444 |
100 | 002A | 鈴木さん | 1234567890 | 入門HTML | 555 |
出版社テーブル
主キー
出版社ID
出版社ID | 出版社名 |
---|---|
100 | A社 |
200 | B社 |
300 | C社 |
第二正規形書籍テーブルの作成
CREATE TABLE BOOK_N2 ( COMPANY_ID INTEGER NOT NULL, AUTHOR_ID CHAR(10) NOT NULL, AUTHOR_NAME VARCHAR(255) NOT NULL, ISBN10 BIGINT NOT NULL, BOOK_NAME VARCHAR(255) NOT NULL, BOOK_PRICE NUMERIC, PRIMARY KEY(COMPANY_ID, ISBN10) ); INSERT INTO BOOK_N2 VALUES (100, '001A', '鈴木さん', 9987654321, '入門C言語', 111); INSERT INTO BOOK_N2 VALUES (200, '001B', '加藤さん', 1111111111, '入門JAVA', 222); INSERT INTO BOOK_N2 VALUES (200, '001B', '加藤さん', 2222222222, '入門Scala', 333); INSERT INTO BOOK_N2 VALUES (300, '001C', '高橋さん', 1111111111, '入門Ruby', 444); INSERT INTO BOOK_N2 VALUES (100, '002A', '鈴木さん', 1234567890, '入門HTML', 555);
出版社テーブルの作成
CREATE TABLE COMPANY ( COMPANY_ID INTEGER NOT NULL, COMPANY_NAME VARCHAR(255), PRIMARY KEY (COMPANY_ID) ); INSERT INTO COMPANY VALUES (100, 'A社'); INSERT INTO COMPANY VALUES (200, 'B社'); INSERT INTO COMPANY VALUES (300, 'C社');
第三正規形
第二正規形が終わり、すべての非キーの列は主キー列と関数従属になりました。
しかし、この第二正規形の書籍テーブルで問題になるのは、本を出版していない著者を登録できないことです。
今回は本棚の本の整理ということで話を進めてきましたが、例えば出版社名の部分が汚れで読めないので、著者だけでも登録したい。といったことができない。といったことが起こります。
ここから第三正規形を行っていきます。
第二正規化された書籍テーブルの著者IDと著者名に注目します。
主キー:出版社ID → 著者ID → 著者名
の関係が存在します。
このように段階的な関数従属があるとき、これを推移的関数従属と呼びます。
第三正規形ではこの推移的関数従属を取り除きます。
以下に第三正規化された書籍テーブルと、新しく作った著者テーブルを載せます。
書籍テーブル
主キー
出版社ID,ISBN10
外部キー
出版社ID,著者ID
出版社ID | 著者ID | ISBN10 | 書籍名 | 価格 |
---|---|---|---|---|
100 | 001A | 9987654321 | 入門C言語 | 111 |
200 | 001B | 1111111111 | 入門JAVA | 222 |
200 | 001B | 2222222222 | 入門Scala | 333 |
300 | 001C | 1111111111 | 入門Ruby | 444 |
100 | 002A | 1234567890 | 入門HTML | 555 |
著者テーブル
主キー
著者ID
著者ID | 著者名 |
---|---|
001A | 鈴木さん |
001B | 加藤さん |
001C | 高橋さん |
002A | 鈴木さん |
出版社テーブル
主キー
出版社ID
出版社ID | 出版社名 |
---|---|
100 | A社 |
200 | B社 |
300 | C社 |
第三正規形書籍テーブルの作成
CREATE TABLE BOOK_N3 ( COMPANY_ID INTEGER NOT NULL, AUTHOR_ID CHAR(10) NOT NULL, ISBN10 BIGINT NOT NULL, BOOK_NAME VARCHAR(255) NOT NULL, BOOK_PRICE NUMERIC, PRIMARY KEY(COMPANY_ID, ISBN10) ); INSERT INTO BOOK_N3 VALUES (100, '001A', 9987654321, '入門C言語', 111); INSERT INTO BOOK_N3 VALUES (200, '001B', 1111111111, '入門JAVA', 222); INSERT INTO BOOK_N3 VALUES (200, '001B', 2222222222, '入門Scala', 333); INSERT INTO BOOK_N3 VALUES (300, '001C', 1111111111, '入門Ruby', 444); INSERT INTO BOOK_N3 VALUES (100, '002A', 1234567890, '入門HTML', 555);
著者テーブルの作成
CREATE TABLE AUTHOR ( AUTHOR_ID CHAR(10) NOT NULL, AUTHOR_NAME VARCHAR(255) NOT NULL, PRIMARY KEY(AUTHOR_ID) ); INSERT INTO AUTHOR VALUES ('001A', '鈴木さん'); INSERT INTO AUTHOR VALUES ('001B', '加藤さん'); INSERT INTO AUTHOR VALUES ('001C', '高橋さん'); INSERT INTO AUTHOR VALUES ('002A', '鈴木さん');
可逆的
第二正規化、第三正規化でテーブルの分割を行いましたが、この分割は無損失分解のため、第二正規化と第三正規化は可逆的な操作です。
実際に分解されたテーブルをSQLで結合することにより、もとに戻してみました。
第二正規形から第一正規形
SELECT N2.COMPANY_ID, COMPANY.COMPANY_NAME, N2.AUTHOR_ID, N2.AUTHOR_NAME, N2.ISBN10, N2.BOOK_NAME, N2.BOOK_PRICE FROM BOOK_N2 AS N2 INNER JOIN COMPANY ON COMPANY.COMPANY_ID = N2.COMPANY_ID;
第三正規形から第二正規形
SELECT N3.COMPANY_ID, N3.AUTHOR_ID, AUTHOR.AUTHOR_NAME, N3.ISBN10, N3.BOOK_NAME, N3.BOOK_PRICE FROM BOOK_N3 AS N3 INNER JOIN AUTHOR ON AUTHOR.AUTHOR_ID = N3.AUTHOR_ID;
第三正規形から第一正規形
SELECT N3.COMPANY_ID, COMPANY.COMPANY_NAME, N3.AUTHOR_ID, AUTHOR.AUTHOR_NAME, N3.ISBN10, N3.BOOK_NAME, N3.BOOK_PRICE FROM BOOK_N3 AS N3 INNER JOIN COMPANY ON COMPANY.COMPANY_ID = N3.COMPANY_ID INNER JOIN AUTHOR ON AUTHOR.AUTHOR_ID = N3.AUTHOR_ID;
正規化のメリット・デメリット
メリット
1.データの冗長性が排除され不整合が起きにくくなる
2.テーブルの持つ意味が理解しやすくなる。(エンティティ)
デメリット
1.テーブルの数が増えることによるパフォーマンスの悪化