Upload
others
View
9
Download
0
Embed Size (px)
Citation preview
注文データの正規化について
学籍番号: 12345678名前: 姫路太郎
e-mail: [email protected]
2016/09/30
1 問題の設定
ある店が一定期間に得た注文のデータについて考える。データは何件かの注文票の集まりであり、注文票す
なわちレコードにはつぎの項目(フィールド)がある。
レコード内の項目� �注文番号,注文日,顧客コード,顧客名,電話番号,商品コード (商品名,単価,数量,小計) ,· · ·,商品コード(商品名,単価,数量,小計)� �「注文」1件につき、注文した顧客に関する情報、および1つ以上の注文した商品情報などが含まれる。す
なわち、一回の注文で2種類以上の商品を注文することができ、1行のレコード内に商品情報が繰り返し現れ
ることが許されるわけである。
2 注文データの正規化
このデータをデータベースに格納できるように正規化していく。まず、注文データには、商品情報が繰り返
し現れているので、第一正規形ではない。
2.1 第1正規形
繰り返し項目を排除するために、表を2つに分けて第1正規形にする。商品に関する情報を別表にする。
元表の項目� �注文番号,注文日,顧客コード,顧客名,電話番号,商品コード,数量� �「小計」はいつでも計算可能なので保持しておく必要はない。
別表:商品表の項目� �商品コード,商品名,単価� �ここに、下線は「主キー」を表す。
1
2.2 第2正規形
第2正規形とは、主キー項目以外の項目が主キー項目に完全関数従属するようにすることである。
商品表については、「商品名」項目、「単価」項目は、それぞれ主キーである「商品コード」に完全関数従属
しているのですでに第2正規形である。さらに、「商品名」項目と「単価」項目の間には関数関係はなく、推移
的関数従属ではないので、第3正規形である。
問題は元表である。主キーを調べると以下の下線部で示した3つの項目であることがわかる。すなわち、「注
文番号」、「顧客コード」、「商品コード」の3つの項目の値を同時に決めなければ行がひとつに定まらない。こ
れら3つの項目がまとまって1つの主キーを構成するのである。
元表の項目� �注文番号,注文日,顧客コード,顧客名,電話番号,商品コード,数量� �
しからば非主キー項目たとえば「注文日」は主キーに関数従属しているであろうか。主キーである「注文番
号」、「顧客コード」、「商品コード」の値が決まれば「注文日」の値が決まるので、確かに「注文日」は主キー
に関数従属している。
「注文日」の関数従属関係� �注文番号,顧客コード,商品コード =⇒ 注文日� �しかし、「注文日」は「顧客コード」や「商品コード」の値と無関係に「注文番号」の値だけできまる。この
ような関数従属関係は部分関数従属である。したがって、「注文日」項目は元表から取り除いて別表にすべき
である。
他の非主キー項目「顧客名」、「電話番号」についても同様にしてわけるとつぎのように3つの表になる。
注文表の項目� �注文番号,顧客コード,商品コード,数量� �注文明細の項目� �注文番号,注文日� �顧客表の項目� �顧客コード,顧客名,電話番号� �
それに最初に分けた商品表
商品表の項目� �商品コード,商品名,単価� �
を併せて全部で4つの表にする。
以上で、どの表も非主キー項目が主キーに完全関数従属する第2正規形になっていることがわかる。
2
2.3 第3正規形
非主キー項目間に関数関係が無いようにするのが第3正規形への変形である。2つ以上の非主キー項目を有
するのは顧客表と商品表であるが商品表はすでにのべたように第3正規形になっている。顧客表について調べ
ると、非主キー項目「顧客名」と「電話番号」の間に関数関係は無いので第3正規形になっている。「顧客名」
が決まったからといって「電話番号」が決まるとは限らない。同姓同名を考えよ。
すなわち、上の4つの表はすべて第3正規形の条件を満たしている。内容があまり変化しないデータはマス
ター系、日々変化するようなデータはトランザクション系と呼ばれる。各表の性質はつぎの表 1のようになる。
表 1 表の性質
注文表 トランザクション系
注文明細 トランザクション系
顧客表 マスター系
商品表 マスター系
3 データベースへの実装
関係データベースへデータを実装することを考えよう。与えられたデータは構造化(正規化)されていない
元データである。
3.1 注文明細
注文明細の項目はつぎのとおりである。
注文明細の項目� �注文番号,注文日� �項目のデータ型を考えると、つぎの SQL文でテーブルを作成できる。注文明細の作成 SQL文� �DROP TABLE 注文明細;
CREATE TABLE 注文明細 (
注文番号 int,
注文日 date
);� �ここでは SQL命令を手作業で行うのではなく、実行すべき SQL命令の列を自動的に生成するプログラム
について考える。与えられたデータを読み、テーブルを作成し、データをテーブルにロードする SQL文を出力するプログラムを作成しよう。
元データ chumon.dataはつぎのようなカンマ区切りのテキストデータである。
3
元データ chumon.dataの一部� �3011,2003/4/14,11040,安富商事,....
3014,2003/4/16,11045,前田商店,....
.....� �最初のフィールドが注文番号で、そのつぎのフィールドが注文日である。プログラムは、テーブルを作成する
CREATE文の出力と、データをロードする INSERT文を繰り返し出力する比較的単純なものとなる。ソースコードのファイル名は mon5a.pyとする。mon5a.pyソース� �ソースコード mon5a.py リスト
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 print '''\
4 DROP TABLE 注文明細 ;
5 CREATE TABLE 注文明細 (
6 注文番号 int,
7 注文日 date
8 );'''
9 file = open('chumon.data')
10 for line in file:
11 x = line[:-1].split(',')
12 print "INSERT INTO 注文明細 VALUES",
13 print "(%s,\'%s\');" % (x[0], x[1])
14 file.close()� �3行目〜8行目がテーブル作成 SQL文の出力であり、9行目から13行目が元データをから INSERT文を
繰り返し作成し出力するものである。実行するとつぎの出力が得られる。
mon5a.pyの実行結果� �DROP TABLE 注文明細 ;
CREATE TABLE 注文明細 (
注文番号 int,
注文日 date
);
INSERT INTO 注文明細 VALUES (3011,'2003/4/14');
INSERT INTO 注文明細 VALUES (3014,'2003/4/16');
............................................� �この SQL文をデータベースサーバ db 上で実行させるには、データベースクライアント psql を使って
4
dbサーバへの登録� �python mon5a.py | psql -h db� �
とする。
実際に登録されているかどうかを調べるには、psqlで dbサーバへ接続してみればよい。dbサーバへの登録状況の確認� �sugiyama@aks3:~/work/mon5$ psql
psql (9.4.9)
"help" でヘルプを表示します.
sugiyama=> \d
リレーションの一覧
スキーマ | 名前 | 型 | 所有者
----------+----------+----------+----------
public | 注文明細 | テーブル | sugiyama
(1 行)
sugiyama=> SELECT * FROM 注文明細;
注文番号 | 注文日
----------+------------
3011 | 2003-04-14
3014 | 2003-04-16
.................
3033 | 2003-05-06
(10 行)� �これで注文明細テーブルが作成されていることが確認できた。
3.2 正規表現を用いた元データの第1正規形への変換
元データを見ると、項目は基本的にカンマ区切りであるが、商品情報が、
元データの商品情報の形式� �商品コード (商品名, 単価, 数量, 小計)� �
のように一つの商品に関するデータがカッコで囲まれている。このカッコ内のカンマは、外側の項目間を区切
るカンマとは機能が異なる。
そこで、カッコをとりはずし、カッコ内のカンマの代わりに縦棒を使ってつぎのように変換することを考
える。
元データの商品情報の形式変換� �商品コード | 商品名 | 単価 | 数量� �
5
「小計」はいつでも計算可能なので捨てる。
実際の元データの商品情報はつぎのようであるから
実際の元データの商品情報� �220051(テレビ,89000,3,267000)� �pythonインタープリタで、正規表現を用いてカッコを外すテストをすると、正規表現でカッコを外すテスト� �>>> # -*- coding: utf-8 -*-
...
>>> import re
>>> p = re.compile(r'(\d+)\((\S+?),(\d+),(\d+),\d+\)')
>>> print p.sub(r'\1|\2|\3|\4', '220051(テレビ,89000,3,267000)')
220051|テレビ|89000|3
>>>� �とできることがわかる。これを利用して chumon.datを第1正規形に変換させることができる。ソースコードのファイル名は mon5b.pyとする。
mon5b.pyソース� �ソースコード mon5b.py リスト
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 '''第一正規形に変換'''
4 import re
5 p = re.compile(r'(\d+)\((\S+?),(\d+),(\d+),\d+\)')
6 file = open('chumon.data')
7 for line in file:
8 s = p.sub(r'\1|\2|\3|\4', line[:-1])
9 x = s.split(',')
10 for y in x[5:]:
11 (y1,y2,y3,y4) = y.split('|')
12 print "%s,%s,%s,%s,%s,%s,%s,%s,%s" %\
13 (x[0],x[1],x[2],x[3],x[4],y1,y2,y3,y4)
14 file.close()� �第5行目で正規表現を与えている。第8行目でカッコ内のカンマを一時的に縦棒に変え、第9行目で split()を用いてカンマで区切ってリストにしている。リストは変数 x に蓄えている。x の最初の5個のフィールドは固定であるが、その後ろ6番目以降すなわち x[5:]には縦棒で区切られた商品情報が繰り返し不定個出てくる。第10行目のループで不定個の商品情報をそれぞれ処理している。第11行目以降で、xの最初の5個のフィールドと、商品情報 yから得られる4つのデータを並べてカンマ区切りで出力している。
6
実行するとつぎの出力が得られる。
mon5b.pyの実行結果� �3011,2003/4/14,11040,安富商事,03-3254-6677,220051,テレビ,89000,3
3011,2003/4/14,11040,安富商事,03-3254-6677,330012,肘掛椅子,47000,5
3011,2003/4/14,11040,安富商事,03-3254-6677,330080,コーチ,60000,2
3014,2003/4/16,11045,前田商店,03-5678-4321,220058,エアコン,156000,1
....................� �標準出力のリダイレクションを利用して、実行結果をファイル chumon1.datに保存する。mon5b.pyの実行結果の保存� �python mon5b.py > chumon1.dat� �これは、第1正規形になったデータである。以後このデータファイルを用いる。
3.3 注文表の作成
注文表の項目はつぎのとおりである。
注文表の項目� �注文番号,顧客コード,商品コード,数量� �注文表については極めて簡単でデータファイル chumon1.datを1行毎に読み、必要な4つの項目を出力す
るだけである。ソースファイル名は mon5c.pyとする。実行結果は次のように成る。mon5c.pyの実行結果� �DROP TABLE 注文表 ;
CREATE TABLE 注文表 (
注文番号 int,
顧客コード int,
商品コード int,
数量 int
);
INSERT INTO 注文表 VALUES (3011,11040,220051,3);
INSERT INTO 注文表 VALUES (3011,11040,330012,5);
.........................................� �データベースへの登録はつぎのようにする。
mon5c.pyの実行結果の DB登録� �python mon5c.py | psql -h db� �プログラムのソースコードは次の通り:
7
mon5c.pyソース� �ソースコード mon5c.py リスト
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """注文表"""
4 print '''\
5 DROP TABLE 注文表 ;
6 CREATE TABLE 注文表 (
7 注文番号 int,
8 顧客コード int,
9 商品コード int,
10 数量 int
11 );'''
12 file = open('chumon1.dat')
13 for line in file:
14 x = line[:-1].split(',')
15 print "INSERT INTO 注文表 VALUES",
16 print "(%s,%s,%s,%s);" % (x[0], x[2], x[5], x[8])
17 file.close()� �3.4 顧客表の作成
顧客表の項目はつぎのとおり:顧客表の項目� �顧客コード,顧客名,電話番号� �chumon1.datを読み、顧客情報を得ることを考える。問題は同一の顧客データが何回も出現することであ
る。このような場合は、顧客コードをキーとし、顧客名と電話番号を値とする辞書を作成することで、データ
の重複の問題を解決することができる。
顧客情報の辞書� �{顧客コード : 顧客名|電話番号,· · ·}� �プログラムのソースコードは次の通り:
8
mon5d.pyソース� �ソースコード mon5d.py リスト
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """顧客表"""
4 print '''\
5 DROP TABLE 顧客表;
6 CREATE TABLE 顧客表 (
7 顧客コード int,
8 顧客名 varchar(40),
9 電話番号 varchar(16)
10 );'''
11 file = open('chumon1.dat')
12 d1 = {}
13 for line in file:
14 x = line[:-1].split(',')
15 d1[x[2]] = '%s|%s' % (x[3], x[4])
16 file.close()
17 for (k,v) in d1.iteritems():
18 (name,tel) = v.split('|')
19 print "INSERT INTO 顧客表 VALUES",
20 print "(%s,\'%s\',\'%s\');" % (k, name, tel)� �第12行目で空の辞書 d1を作成しておき、第15行目で、顧客コードをキーとし、’顧客名|電話番号’を値として辞書 d1につぎつぎに登録していく。辞書のキーはユニークであるから、同じ内容の項目が重複して登録されることはないことが保証される。
mon5d.pyの実行結果はつぎのようになる。mon5d.pyの実行結果� �DROP TABLE 顧客表;
CREATE TABLE 顧客表 (
顧客コード int,
顧客名 varchar(40),
電話番号 varchar(16)
);
INSERT INTO 顧客表 VALUES (11068,'小村美容室','0423-34-2340');
INSERT INTO 顧客表 VALUES (11060,'浜田精肉店','0487-33-4567');
................................................� �データベースへの登録は前と同じようにすればよい。
9
3.5 商品表の作成
商品表の項目はつぎのとおり:商品表の項目� �商品コード,商品名,単価� �
商品表については顧客表の場合と同様である。商品コードをキー、’商品名|単価’を値とする辞書を作成すれば良い。
商品情報の辞書� �{商品コード : 商品名|単価,· · ·}� �プログラムのソースコードは次の通り:mon5e.pyソース� �ソースコード mon5e.py リスト
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """商品表"""
4 print '''\
5 DROP TABLE 商品表;
6 CREATE TABLE 商品表 (
7 商品コード int,
8 商品名 varchar(40),
9 単価 int
10 );'''
11 file = open('chumon1.dat')
12 d2 = {}
13 for line in file:
14 x = line[:-1].split(',')
15 d2[x[5]] = '%s|%s' % (x[6], x[7])
16 file.close()
17 for (k,v) in d2.iteritems():
18 (name,price) = v.split('|')
19 print "INSERT INTO 商品表 VALUES",
20 print "(%s,\'%s\',%s);" % (k, name, price)� �第12行目で空の辞書を作成し、つぎつぎに第15行目で商品データを登録していく。
mon5e.pyの実行結果はつぎのようになる。
10
mon5e.pyの実行結果� �DROP TABLE 商品表;
CREATE TABLE 商品表 (
商品コード int,
商品名 varchar(40),
単価 int
);
INSERT INTO 商品表 VALUES (220076,'ステレオ',57000);
INSERT INTO 商品表 VALUES (330055,'学習机',48000);
...............................................� �データベースへの登録は前と同じようにすればよい。
4 データベースの問合せ
一旦データベースへのデータの登録ができたら種々の検索が可能となる。作成された4つの表から検索する
例を示す。ここでは、データベースの利用例としてつぎの問題を考える。[問題] 顧客コードが与えられたとき、その顧客が注文した履歴をリストにして表示せよ。
ここで重要な手法は、外部キーによる表の等結合を用いることである。
顧客コード 11060が与えられたとしよう。顧客情報はつぎのように求められる。顧客情報の検索結果� �sugiyama=> SELECT * FROM 顧客表 WHERE 顧客コード = 11060;
顧客コード | 顧客名 | 電話番号
------------+------------+--------------
11060 | 浜田精肉店 | 0487-33-4567
(1 行)� �この顧客が注文した履歴は注文表を検索することによって得られる。
特定の顧客の注文履歴の検索結果� �sugiyama=> SELECT 注文番号, 商品コード, 数量
sugiyama-> FROM 注文表
sugiyama-> WHERE 顧客コード = 11060;
注文番号 | 商品コード | 数量
----------+------------+------
3017 | 330056 | 2
3017 | 330080 | 5
3033 | 220058 | 2
3033 | 220051 | 5
(4 行)� �しかし、これでは注文日や商品名は別に調べなければわからない。そこで、上の検索結果に注文日や商品名も
11
併せて表示させるようにしたい。そのようなときに等結合が使える。注文日は注文明細を、商品名は商品表を
見なければわからないので、注文表と注文明細と商品表の直積を求めて、その中から必要な行のみを出力させ
る方法である。
特定の顧客の注文履歴の検索結果� �sugiyama=> SELECT c.注文番号, cm.注文日, c.商品コード, g.商品名, c.数量
sugiyama-> FROM 注文表 c, 注文明細 cm, 商品表 g
sugiyama-> WHERE c.顧客コード = 11060
sugiyama-> AND c.注文番号 = cm.注文番号
sugiyama-> AND c.商品コード = g.商品コード;
注文番号 | 注文日 | 商品コード | 商品名 | 数量
----------+------------+------------+--------------+------
3017 | 2003-04-19 | 330080 | コーチ | 5
3017 | 2003-04-19 | 330056 | 革張ソファー | 2
3033 | 2003-05-06 | 220051 | テレビ | 5
3033 | 2003-05-06 | 220058 | エアコン | 2
(4 行)� �正規化によってデータが複数の表に分かれていても等結合を用いれば、どんな問合せにも対応できることが分
かる。
5 結語
データベースへデータを登録するには、データが構造化されていなければならない。構造化のためには正規
化の理論が極めて重要である。正規化とは簡単に言うとデータの重複をなくし整合的にデータを取り扱えるよ
うにデータベースを設計することである。これによってデータの追加・更新・削除などに伴うデータの不整合
や喪失が起きるのを防ぎ、メンテナンスの効率を高めることができる
本稿では「注文データ」という具体的な構造化されていないデータを用いてそれを段階的に正規化していく
過程を示した。正規化を行うには、主キーや関数従属などの基本概念の理解が必要不可欠であると認識し、実
際のデータのハンドリングにおいてはプログラミングの技術が極めて有効であることを身を持って認識した。
特に、正規表現の利用はテキスト処理において極めて重要で強力な武器になり得ることを確認することがで
きた。
また適切に正規化されたシステムでは、データの追加や削除が整合性を失うことなくできるようになるのに
加えてどのような問合せにも対応できることを確認した。。
12