9.2 理解・分解・再構築

(1/1)
レンチキュラー印刷のイラスト
スプレッドシートをただデータベースに登録するのではなく、データベースを正しく設計することで、前回見えてきた課題を解決することができる。
そこで今回は、理解・分解・構築という流れでデータベースを設計し、Excelに集めた成績表などのデータをCSVファイルにしてデータベースを構築するプログラムを作る。SQL は使用しない。

目次

サンプル・プログラム

圧縮ファイルの内容
csv2db.pyCSVファイルをDBに登録するプログラム
testScore.csv成績表CSVファイル
tableStudent.csv児童テーブル作成用CSVファイル
tableTestScore.csv得点テーブル作成用CSVファイル
tableEvent.csvイベント・テーブル作成用CSVファイル
masterSex.csv性別マスタ作成用CSVファイル
masterSubject.csv科目マスタ作成用CSVファイル
masterTest.csvテスト・マスタ作成用CSVファイル
masterEvent.csvイベント・マスタ作成用CSVファイル

理解・分解・再構築

荒川弘さんの漫画『鋼の錬金術師』第22話「仮面の男」で、主人公の1人で錬金術師のエドワード・エルリックが次のようにつぶやく――
オレもアルもその大きい流れの中のほんの小さなひとつ――全の中の一。だけど、その一が集まって全が存在する。この世は想像もつかない大きな法則に従って流れている。その流れを知り、分解して再構築する‥‥それが錬金術。
RDBMS もプログラムと同じように設計作業が必要だ。そして、リアル世界にあるデータを漏れなく、かつ二重管理にならないよう登録するために、錬金術と同じ「理解・分解・再構築」という手順を踏む。データベース技術を習得した方なら、「理解=外部スキーマ、分解=概念スキーマ、再構築=物理スキーマ」という対応関係で認識しておいてほしい。

課題の理解

前回の最後に、スプレッドシートを使って小学校の成績表を管理するには、さまざまな課題があることが浮かび上がった。まず、こうした課題を整理し、理解するところから着手しよう。

まず、このデータベースが扱うデータの範囲を決める。これをスコープ(範囲)と呼ぶ。(※現実には定期テストのない小学校がほとんどだが、ここでは説明の都合上、期末テストが行われるものとする)
そこで、次のスコープを設定する。
  1. 1人1人の児童のテストの得点を記録する。
  2. 各科目毎の得点として記録する。
  3. テストは期末テストだけでなく小テストなども含む。
  4. 科目は、最大で、国語・算数・理科・社会の4科目とする。
  5. 対象は当校の全学年・全クラスとする。
  6. データ登録期間は直近10年間分とする。
  7. 転出・転入、また退学や死亡した児童のデータも全て記録として残す。
これらをExcelのようなスプレッドシートで記録しようとすると、どんな課題が想定されるだろうか。思いつくものをリストアップしてみよう。
  • 1年生、2年生は理科、社会の期末テストがないので、スプレッドシートの書式が一定しない。
  • 小テストはクラス毎に1つまたは複数の科目で行われ、実施日・実施回数もクラスによって異なり、1つのワークシートに記入しきれない。
  • 全学年全クラスの直近10年分のデータを登録しようとすると、ワークシート数が膨大になるか、1つの巨大なワークシートになってしまい、データの入力ミスがあっても気付きにくくなる。
  • 転出すると、それ以降のレコードやカラムが空白になってしまう。
  • 転入するとき、いちいちレコードを追加しなければならない。また、転入する前の集計に含めないようにしなければならない。

児童と答案用紙に分解

前述の課題は、大きく2つのグループに分けることができる。
  1. データ構造に起因‥‥空のカラム、ワークシートが膨大など
  2. 児童の動態に起因‥‥転出、転入など
テストを返す先生のイラスト
ここで、実際に答案用紙を返却するシーンを想像してほしい。
1科目1枚の答案用紙に点数が記入され(複数枚の科目もあるかもしれないが点数が記入されているのは1枚)、1人の児童に返却される。当たり前の話だが、児童と答案用紙はひもか何かで繋がっているわけではなく、いつでも分離できる。
つまり、児童と点数を1つのワークシート上で結びつけて管理しようという考え方が実体(リアル世界)に即していないのだ。
さらに言えば、答案用紙は1科目1枚だから、4科目を1枚のスプレッドシートにまとめようというのも、実体から乖離している。
そこで実体に即すよう、児童と答案用紙を別々のオブジェクト(データベースで言うところのテーブル)に分解し、さらに、児童1人を1レコードに、答案用紙1枚を1レコードに分解する方針を立てる。このように、現実世界にある実体に即したオブジェクトに置き換えてプログラムを作ることをオブジェクト指向と呼ぶ。

この小学校は、1学年100~120名、全学年で600~720名、10年分の児童は6,000~7,200名となるから、児童の方は1万レコードあれば十分だ。
一方、テストはというと、この人数に、4教科、1学年に期末テストだけで3回、小テストを含めると7~8回はあるだろう。となると、7,200×4×8=23万レコード以上となる。
Excelで23万行もあるようなワークシートは滅多にお目にかからない。だがご安心を。RDBMS はレコード数が数百万行あってもストレスなく動作するよう作られている。

さらに細かく分解

方針として、児童と答案用紙(点数)を別々に分けて管理することが決まった。それぞれの情報について、さらに細かく見ていこう。
4年生の名簿
まず児童名簿の方だが、前回紹介した4年生の名簿をベースに考えてみる。
この児童名簿には、課題に挙げた中で、転入、転出、退学、死亡に関する情報が不足している。また、学年、クラス、出席番号は毎年更新されていくので、これらの情報は児童名簿から分離した方がよさそうだ。

まず、児童名簿は、児童の属性を記録する役割に絞ることにする。転入、転出、退学、死亡は、児童に対して随時発生するイベント(学校や児童が発生時期をコントロールできない)なので、別のオブジェクトに分けた方が現実に即しているからだ。学年、クラス、出席番号が変わるのも、進学、進級といったイベントに伴って発生するものだ。
そこで、児童のイベントを記録する役割をもたせたオブジェクトを追加する。
分解→3つのテーブル
分解→3つのテーブル
次にテスト結果の方だが、前述のように答案用紙と1対1対応するオブジェクトにする。すなわち、1レコードには、児童1人が、ある日受けたテストの、1科目の得点を記録する。

3つ目はイベント情報だ。児童の転入・転出・進級・進学などのイベントが発生した年月日、学年・クラスなどを記録する。

これら3つのオブジェクトを図示すると、左図のようになる。Pythonでは、この3つをオブジェクトとして扱うが、RDBMS の用語としてテーブルという接尾辞を付けた。繰り返しになるが、これはExcelのワークシートのようなものだと考えてほしい。

オブジェクトの構造

児童テーブル、得点テーブル、イベント・テーブルの3つのオブジェクトに記録する情報――オブジェクトの構造――を深堀りいしていこう。

児童テーブル
  • 学籍番号🔑
  • 姓名
  • 姓名のよみ
  • 性別
  • 生年月日
離婚・再婚などによって苗字が変わることがある。そこで、姓と名は別々の変数に分解しておこう。同様に、よみも姓と名に分離する。

ここで🔑マークに注目してほしい。
データベースでは、そのレコードを他のレコードから識別するためのユニークなデータをキーと呼ぶ。そして、SQLite の場合、かならずキーを1つ設定しなければならない。
児童テーブルでは、学籍番号がキーになるのは自明だ。

性別であるが、常識的には「男」「女」の2値で十分なのだが、こういった世界共通の属性は世界標準に従うのが、システム開発の王道だ。
国内規格であるJISでは、1971年(昭和46年)に制定された JIS X0303 が性別を標準化しており、「男=1, 女=2」というコード(数字)で表すことを定めていた。
ところが、1976年(昭和51年)11月に国際標準か機構(ISO)が、生物学的な性別を下表の4値で表すことを勧告した。
コード🔑意味日本語訳
0not known不明
1male男性
2female女性
9not applicable適用不能
このコード表は、のちに ISO 5218 として国際標準化され、前述の JIS X0303 は廃止となった。当時のISOも、さすがに日本を含む複数の国で「男=1, 女=2」というコード体系を利用していたことを把握していたから、その体系を壊さないよう、上位互換のコード体系になっている。
近年話題のジェンダー問題を意識しているわけではないが、ここでは ISO 5218 という国際標準を採用することにする。

さて、児童テーブルに記録するのが ISO 5218コードになると、児童一覧を画面表示するとき、0, 1, 2, 9 の数字だけでは性別が伝わりにくい。そこで、コードから意味(日本語訳)を引けるコード表オブジェクトを別に用意する必要がある。このようなコード表オブジェクトを、データベース用語でマスタと呼ぶ。ISO 5218 のコード表は性別マスタと呼ぶことにする。
キーはコードである。マスタの場合、コードやIDが必ずキーになる。

得点テーブル
  • 学籍番号🔑
  • テストID🔑
  • 科目ID🔑
  • 得点
得点テーブルには児童の姓名を記載せず、学籍番号で児童に紐付ける。児童の姓名は、上述の児童テーブルに記録されているから、得点テーブルに記録すると二重管理になったり転記ミスを誘発するからだ。このように、同じ情報が異なるオブジェクトで二重管理することのないよう、分解作業を通じて留意する。
実施されたテストはID番号で、科目もこれとは別のID番号で管理する。したがって、テストIDからテストの実施日や実施内容を引くことができるコード表オブジェクト「テスト・マスタ」と、科目IDから何の科目かを引くことができる「科目マスタ」が必要になる。マスタというのは、コードやID番号から実体を引く辞書のようなテーブルだと覚えておいてほしい。

得点テーブルのキーは、結論から言うと、学籍番号、テストID、科目IDの3つを一括してしてキーにする。同じ児童が、同じテストの同じ科目を受けることはないからだ。このように複数のカラムを一括してにすることを複合キーと呼ぶ。

イベント・テーブル
  • 学籍番号🔑
  • 発生日🔑
  • イベントID🔑
  • 学年
  • クラス
  • 出席番号
イベント・テーブルも学籍記番号を解して児童に紐付ける。転入、転出、進級‥‥といったイベントもIDで管理する。したがって、イベントIDからイベント内容を引くことができるコード表オブジェクト「イベント・マスタ」が必要になる。また、イベントの発生日、イベント発生後の学年、クラス、出席番号を記録できるようにする。

イベント・テーブルのキーは、学籍番号、発生日、イベントIDの3つを複合キーにする。同じ児童が、同じ日にイベントを発生させることはないからだ。もし同じイベントを発生させるようなことがあったら、最後のイベントを記録する。

この段階では、とくにキーの設定に留意してほしい。どうしてもキーが見つからないようだったら、「さらに細かく分解」をやり直すこと。おそらく分解が足りていないからだ。

これで、3つのテーブルと4つのマスタが出揃った。下図に整理しておこう。
分解→3つのテーブルと4つのマスタ
分解→3つのテーブルと4つのマスタ
すべてのテーブルとマスタに、レコードを初めて記録した登録日時と、変更した更新日時、それから削除したことを意味する削除フラグを追加している。
今回のスコープにでは、これらのテーブルは10年間保存であるから、数年ほど過去に遡ってデータを参照するケースが想定される。そのとき、いつ登録されたか、いつ変更されたかが分かると、何かの確認作業の役に立つからだ。
実運用では「誰が」という情報も付け加える必要があるのだが、通常、データベースにログインしたアカウントを「誰が」に割り当てる。これからつくるプログラムではログイン管理を実装しないので「誰が」の情報はなく、実装は割愛する。各自が課題として取り組んでほしい。詳しくは「[#column_5W1H":title=コラム:確証としての5W1H]」をご覧いただきたい。

オブジェクトの再構築

3つのテーブルと4つのマスタに分解できたところで、これらのオブジェクトを再構築する。理解・分解・再構築の最終段階だ。
ここでいう再構築とは、上述のように、得点テーブルの科目コードから得点マスタの科目名を引くような、オブジェクト同士の関係を定義する作業である。
RDBMS の頭文字 R は Relation(関係)の R だ。RDBMS は「関係データベース」と訳されるが、それは、テーブル(オブジェクト)同士の関係を定義できることに由来する。ExcelのVLOOKUP関数を使いやすくしたような仕組みである。

3つのテーブルと4つのマスタの関係を表すと下図のようになる。
再構築→関係図
再構築→関係図

解説:CSVファイルをデータベースに登録するプログラム

各クラス担任から得点を集め、Excel上に7つのワークシート――前述の3つのテーブルと4つのマスタ――に整理できたとする。それぞれをCSVファイルに出力したものが、配布は要るに同梱してある。これをデータベースに登録するプログラムが "csv2db3.puy" である。
カレントディレクトリに、"csv2db3.puy" とCSVファイル7つを配置し、このプログラムを実行すると、"testScore2.sqlite3" というデータベースが出来上がる。DB Browser for SQLite を遣って "testScore2.sqlite3" を見ると、7つのテーブルが出来上がっていることがわかる。
csv2db3.py
import sqlite3
from datetime import datetime

try: import pandas as pd from sqlalchemy import create_engine, Table, PrimaryKeyConstraint from sqlalchemy import Column, Integer, Float, String, Text, Boolean from sqlalchemy.orm import sessionmaker, declarative_base except ImportError as errmsg: print(f"外部ライブラリをインストールしてください ... {errmsg}") exit()
では、"csv2db2.py" をご覧いただきたい。
モジュールの import は、前回紹介した通りである。外部モジュール SQLAlchemy の PrimaryKeyConstraint, Text, Boolean などのクラスを使うので、追加してある。
csv2db3.py
# 初期値(START) =============================================================
# CSVファイルのエンコード = シフトJIS
csvEncoding = "shift_jis"

# DBパス:SQLiteが利用するファイル名 databaseURI = "sqlite:///testScore2.sqlite3"
# ベースモデル Base = declarative_base()
# 初期値(END) ================================================================
初期値は前回と同じなので説明は割愛する。
csv2db3.py
# テーブル定義(START) ========================================================
# 児童テーブル
class tableStudent(Base):
	__tablename__	= "tableStudent"							# テーブル名
	id				= Column(String(20), primary_key = True)	# 学籍番号
	lastName		= Column(String(50))						# 姓
	firstName		= Column(String(50))						# 名
	lastNameYomi	= Column(String(50))						# 姓よみ
	firstNameYomi	= Column(String(50))						# 名よみ
	sex				= Column(String(2))							# 性別コード
	birth			= Column(String(10))						# 生年月日
	regist			= Column(String(30))						# 登録日時
	update			= Column(String(30))						# 更新日時
	delete			= Column(Boolean)							# 削除フラグ

# 得点テーブル class tableTestScore(Base): __tablename__ = "tableTestScore" # テーブル名 id = Column(String(20)) # 学籍番号 testID = Column(String(50)) # テストID subjectID = Column(String(6)) # 科目ID score = Column(Integer) # 得点 regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ # 複合プライマリーキーを定義 __table_args__ = ( PrimaryKeyConstraint("id", "testID", "subjectID"), )
# イベント・テーブル class tableEvent(Base): __tablename__ = "tableEvent" # テーブル名 id = Column(String(20)) # 学籍番号 date = Column(String(10)) # 発生日 eventID = Column(String(6)) # イベントID grade = Column(String(4)) # 学年 cclass = Column(String(4)) # クラス attendNumber = Column(String(6)) # 出席番号 regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ # 複合プライマリーキーを定義 __table_args__ = ( PrimaryKeyConstraint("id", "date", "eventID"), )
# 性別マスタ class masterSex(Base): __tablename__ = "masterSex" # テーブル名 code = Column(String(2), primary_key = True) # ISO5218コード label = Column(String(20)) # 表示テキスト regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ
# 科目マスタ class masterSubject(Base): __tablename__ = "masterSubject" # テーブル名 id = Column(String(2), primary_key = True) # 科目ID label = Column(String(20)) # 表示テキスト regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ
# テスト・マスタ class masterTest(Base): __tablename__ = "masterTest" # テーブル名 id = Column(String(10), primary_key = True) # テストID label = Column(String(20)) # 表示テキスト date = Column(String(10)) # 実施日 comment = Column(Text) # 備考 regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ
# イベント・マスタ class masterEvent(Base): __tablename__ = "masterEvent" # テーブル名 id = Column(String(2), primary_key = True) # イベントID label = Column(String(20)) # 表示テキスト regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ
# オブジェクト(テーブル)一覧 objectList = [ tableStudent, tableTestScore, tableEvent, masterSex, masterSubject, masterTest, masterEvent ];
# テーブル定義(END) ==========================================================
3つのテーブルと4つのマスタを、前回同様、クラスで定義する。
単一のキーprimary_key で指定し、複合キーは [PrimaryKeyConstraint] を使って指定する。String型の最大長は、適当に設定している。データベース技術を習得した方なら、これは varchar の設定値に変換されると覚えておいてほしい。
最後に、CSVファイルをまとめて読み込むときに利用できるよう、クラス一覧をリストの形で objectList に代入しておく。
csv2db3.py
# テーブル定義(START) ========================================================
# 児童テーブル
class tableStudent(Base):
	__tablename__	= "tableStudent"							# テーブル名
	id				= Column(String(20), primary_key = True)	# 学籍番号
	lastName		= Column(String(50))						# 姓
	firstName		= Column(String(50))						# 名
	lastNameYomi	= Column(String(50))						# 姓よみ
	firstNameYomi	= Column(String(50))						# 名よみ
	sex				= Column(String(2))							# 性別コード
	birth			= Column(String(10))						# 生年月日
	regist			= Column(String(30))						# 登録日時
	update			= Column(String(30))						# 更新日時
	delete			= Column(Boolean)							# 削除フラグ

# 得点テーブル class tableTestScore(Base): __tablename__ = "tableTestScore" # テーブル名 id = Column(String(20)) # 学籍番号 testID = Column(String(50)) # テストID subjectID = Column(String(6)) # 科目ID score = Column(Integer) # 得点 regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ # 複合プライマリーキーを定義 __table_args__ = ( PrimaryKeyConstraint("id", "testID", "subjectID"), )
# イベント・テーブル class tableEvent(Base): __tablename__ = "tableEvent" # テーブル名 id = Column(String(20)) # 学籍番号 date = Column(String(10)) # 発生日 eventID = Column(String(6)) # イベントID grade = Column(String(4)) # 学年 cclass = Column(String(4)) # クラス attendNumber = Column(String(6)) # 出席番号 regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ # 複合プライマリーキーを定義 __table_args__ = ( PrimaryKeyConstraint("id", "date", "eventID"), )
# 性別マスタ class masterSex(Base): __tablename__ = "masterSex" # テーブル名 code = Column(String(2), primary_key = True) # ISO5218コード label = Column(String(20)) # 表示テキスト regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ
# 科目マスタ class masterSubject(Base): __tablename__ = "masterSubject" # テーブル名 id = Column(String(2), primary_key = True) # 科目ID label = Column(String(20)) # 表示テキスト regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ
# テスト・マスタ class masterTest(Base): __tablename__ = "masterTest" # テーブル名 id = Column(String(10), primary_key = True) # テストID label = Column(String(20)) # 表示テキスト date = Column(String(10)) # 実施日 comment = Column(Text) # 備考 regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ
# イベント・マスタ class masterEvent(Base): __tablename__ = "masterEvent" # テーブル名 id = Column(String(2), primary_key = True) # イベントID label = Column(String(20)) # 表示テキスト regist = Column(String(30)) # 登録日時 update = Column(String(30)) # 更新日時 delete = Column(Boolean) # 削除フラグ
# オブジェクト(テーブル)一覧 objectList = [ tableStudent, tableTestScore, tableEvent, masterSex, masterSubject, masterTest, masterEvent ];
# テーブル定義(END) ==========================================================
3つのテーブルと4つのマスタを、前回同様、クラスで定義する。
単一のキーprimary_key で指定し、複合キーは [PrimaryKeyConstraint] を使って指定する。String型の最大長は、適当に設定している。データベース技術を習得した方なら、これは varchar の設定値に変換されると覚えておいてほしい。
最後に、CSVファイルをまとめて読み込むときに利用できるよう、クラス一覧をリストの形で objectList に代入しておく。
csv2db3.py
def getPandasDtypes(obj):
	"""カラム名から対応するpandasのdtypeを取得する

Args: obj(obj): SQLAlchemyのクラスとして定義したオブジェクト(テーブル) Returns: list: pandasのdtypeリスト """ # SQLAlchemyデータ型をpandasデータ型に変換する辞書 sqlalchemy2pandasDtype = { Boolean: "bool", Integer: "Int64", Float: "Float64", String: "str", Text: "str" }
pandasDtypes = {} for column in obj.__table__.columns: # データ型クラスを取得して、対応するpandasのdtypeに変換 colType = type(column.type) pandasDtype = sqlalchemy2pandasDtype.get(colType, "object") # デフォルトは "object" pandasDtypes[column.name] = pandasDtype return pandasDtypes
ユーザー定義関数 getPandasDtypes は、SQLAlchemy の各カラムのデータ型を、外部モジュール pandas を遣ってCSVファイルを読み込んだときの各カラムのデータ型と一致させるために、1つのオブジェクト(テーブル)分の pandas のデータ型(dtype)をリストとして返すものである。関数内に用意した 辞書 sqlalchemy2pandasDtype を遣って紐付けを行う。
SQLAlchemypandas でよくうデータ型を、一覧表に整理しておく。
pandasの主なデータ型
データ型意味Pythonのデータ型
bool真偽値bool
int64整数int
float64浮動小数float
str文字列str
pandas には int や float というデータ型もあるのだが、null値に対応していないため、上述のデータ型を用いることにする。
SQLAlchemyの主なデータ型
データ型意味Pythonのデータ型
Boolean真偽値bool
Integer整数int
Float浮動小数float
String(最大文字数)比較的短い文字列str
Text長い文字列str
csv2db3.py
メイン・プログラムの冒頭は、前回同様、SQLiteエンジンを作成し、DBを作成(create)する。
先ほど用意したオブジェクト(テーブル)一覧 objectList を [for文;blue]を使って1つずつ取りだし、まず、カラム名とデータ型のリスト columnList と、これに対応する pandasのdtypeリスト pandasDtypes を用意する。そうしたら、前回同様 pandas.read_csvメソッドを使って一気にCSVファイルを読み込む。
次に、前回と同様にデータを一括でインサートする。
前回と異なるのは、CSVファイル名を、クラスの __tablename__ と同じにしていることと、各CSVファイルの1行目をカラム名にしていることである。こうすることで、CSVファイルをDBへ一括登録しやすくしている。
今回はデータベースを作るところまでを説明したが、ここまでの説明では、再構築で行ったオブジェクト同士の関係をプログラムに実装することはなかった。これは、次回、データベースを利用するところで実装することになる。

練習問題

次回予告

次回は、今回作ったデータベースを使い、成績の集計などを行っていく。この際、オブジェクト同士の関係が重要になってくる。

コラム:確証としての5W1H

5W1H
本文では、テーブルの登録変更履歴として「いつ」「誰が」が必要であると説明した。より厳格に言えば、確証として利用するには 5W1Hが揃っていた方がいい――つまり、いつ(When)、誰が(Who)、どこで(Where)、なぜ(Why)、何を(What)、どのように(How)の5W1Hだ。このうち、何を(What)は対象レコードになるから割愛できる。
いつ(When)は、本文で説明したように、登録・変更の日時である。より証拠性を高めるならば、タイムスタンプ証明書を登録できるといいだろう。
誰が(Who)は、登録・変更した人物であるから、そのデータベースにログインしたアカウントとイコールである。アカウント名やID属性番号を登録すればいい。より証拠性を高めるならば、そのアカウント固有の電子証明書を登録できるといいだろう。
どこで(Where)は、このデータベースがネット経由でどこからでも登録・変更が可能ならば、ぜひ記録してほしい。いろいろな方式があると思うが、最も簡単で証拠能力もあるものとしては、そのデータベースにログインした端末のIPアドレスであろう。
なぜ(Why)は、登録・変更理由だが、これはテキスト入力してもらうのがよさそうだ。
最後のどのように(How)だが、これは、データベースにPCでログインしたか、スマホでログインしたか。どのようなクライアントアプリを使ってデータベースに手を加えたかなどの手段を自動的に記録できるといいだろう。

本編で扱った科目マスタは、科目IDと科目名という2つのカラムしかないが、これに更に6つのカラムを加えるのは無駄なことに感じるかもしれない。しかし、今回のスコープである10年先の将来を見越して設計するのがデータベースである。10年先の未来にデータベースを利用するシーンを想像し、いまは無駄に感じるかもしれないけれど、将来必要になるデータは絶対に設計に盛り込んでほしい。

マスターについては、有効期間が必要になる場合がある。
たとえばカレンダー・プログラムに祝日を表示しようとすいると、「国民の祝日マスター」を用意するのが定石だ。日本の祝日は、「国民の祝日に関する法律」(祝日法)という法律によって定められている。
ところが、法改正で祝日が新設されたり移動することがある。
たとえば天皇誕生日は、1949年(昭和24年)から1989年までは昭和天皇の誕生日である4月29日だったが、1989年(昭和64年)からは上皇陛下の誕生日である12月23日に、2020年(令和2年)からは現在の天皇陛下の誕生日である2月23日に変化した。4月28日は、1990年(平成2年)から2006年は「みどりの日」に、2007年(平成19年)以降は「昭和の日」に名称変更になっている。
したがって、「国民の祝日マスター」の祝日は、有効期間が必要になる(参考:PHPで祝日を求める)。なお、祝日法が制定されたのは1948年(昭和23年)なので、「国民の祝日マスター」の適用範囲(スコープ)は1948年(昭和23年)以降である。
内閣府の「国民の祝日に関する法律」からダウンロードできるCSVファイルは、マスターではなくテーブルの考え方になっており、毎年の「国民の祝日」が一覧になっている。ただし、法律により、前年2月の官報に確定した祝日を掲載することになっているので、このCSVファイルには、それ以降の祝日が掲載されておらず、万年カレンダー・プログラムから参照するにはには使い勝手が悪い。

コラム:なぜSQLを使わないのか

SQL
私はデータベースとの付き合いが長く、SQL を使い出したのは30年ほど前からである。SQL は、集合論に根ざした洗練された命令体系がゆえに、基本的な命令は、この30年間ほとんど変わっていない。
しかし、本編で紹介したオブジェクト指向とは相性が悪く、オブジェクト指向データベース(OODB)が登場してSQLの牙城を崩しにかかったが、今日なお成功したという話は聞かない。
それほど開発現場に定着している SQL を、今回のシリーズであえて取り上げないのは、その命令体系が強力であるがゆえに、使い方を間違えれば関係データベースを壊すことが簡単にできるし、何よりも Python のオブジェクトとの橋渡しをするのに膨大なプログラムを書かなければならないからだ。
また、これは SQL の参考書でもあまり取り上げられないのだが、SQL の本来の機能を発揮させるには、数学の集合の知識が求められる。
SQL を習得するのは Python を学ぶより時間がかかるので、今回のシリーズでは割愛した次第である。

参考サイト

(この項おわり)
header