\documentclass[a4j]{jarticle} \usepackage{graphicx} \title{データベース設計と制約} \date{\today} \author{森田 亙昭} \begin{document} \maketitle \section{制約} 制約とは、使用可能な値及び他のテーブルとの関係を定義する決まりのことです。 リレーショナルデータベースにおける制約には、以下のようなものがあります。 \begin{itemize} \item 必須制約 \item 一意制約 \item ドメイン制約 \item 参照制約 \end{itemize} これらの制約は、リレーショナルデータベース内では常に満たされていなければ いけません。もし満たしていないようなときはエラーが起こり、その時に行っ ていた処理は中断され、実行前の状態に戻されます。 図1、2、3のようなテーブルを用いて、制約について説明します。 \begin{figure}[h] \begin{center} \includegraphics{customer.eps} \caption{Customer} \end{center} \end{figure} \begin{figure}[h] \begin{center} \includegraphics{commodity.eps} \caption{Commodity} \end{center} \end{figure} \begin{figure}[h] \begin{center} \includegraphics{sales.eps} \caption{Sales} \end{center} \end{figure} \subsection{必須制約} 必須制約とは、ある指定した属性値にNIL(空値)を許さないという決まりです。 たとえば主キーは、必ず空値以外のデータを持っていなければならないという 点で、常に必須制約を満たしていると言えます。顧客を新たに追加しようとし たとき、必須制約のついていない項目(顧客名)は何も指定しなくてもかまいま せんが、主キーである顧客番号を指定せずに追加を行おうとするとエラーとなり ます。 \subsection{一意制約} 一意制約とは、複数のインスタンスが同一の属性値を持つことを許さないとい う決まりです。これも主キーに対して言える制約で、インスタンスを一意に識 別するために、各テーブルにおける主キーである番号は、かならず他とは異な る値でないといけません。売上データを新たに追加した際、もし追加したデー タの主キーである売上番号がすでに存在するならば、その追加作業は中止され、 エラーが表示されます。 \subsection{ドメイン制約} ドメインとは、領域とか範囲とか言う意味です。つまり、ドメイン制約とは、 ある属性値がとりうる範囲に関する決まりです。例えば、商品テーブルにおけ る単価は、常識から考えても1円以上となるはずです。商品を新たに追加したり、 変更するときに、単価を0円に設定したりするとエラーが起こり、その作業は実 行されません。 \subsection{参照制約} 参照制約は、双方向の参照の一貫性に関する制約です。 つまり、異なるテーブル間で、互いにデータを参照しあっているような項目(売上 の顧客番号と顧客の顧客番号などのような関係)において、 矛盾を生じさせてはいけないというものです。 例えば、売上の顧客番号は顧客の顧客番号を参照しています。つまり、顧客の顧 客番号に変更や削除が行われた場合は、それを参照していた売上のほうの顧客番 号にも同じ処理が行われないと行けません。また、新たに売上を追加しようとす る際、顧客番号は必ず顧客に登録されているもでないといけません。 これらの制約が存在しなければ、顧客が削除されたのに売上はそのままで、顧 客が誰だか分からない売上記録が残ってしまったり、売上に対する顧客を間違え てしまったりと言った深刻な問題がおこります。 \section{手続き属性} Jasmineのようなオブジェクトデータベースの他との違いとして、データの格納、 検索だけでなく格納されたデータへの処理もデータベースによって行うことがで きるということがあります。これは手続き属性と呼ばれるデータベース内に定義 される関数によって行います。 手続き属性を使用することで以下のようなことが行えます。 \begin{itemize} \item データの加工 \item 冗長なデータの削除 \item 外部データへのアクセス \item 内部的なデータ表現の隠蔽 \end{itemize} このような手続き属性を使用することにより、複雑な制約などをJasmineで実現 することが可能となります。 \subsection{手続き属性の実現} 手続き属性を使用するには以下の手順が必要になります \begin{enumerate} \item 手続き属性の定義 \item 手続きのコーディング \item コンパイル \end{enumerate} \subsubsection{手続き属性の定義} 手続き属性の定義は、以前説明したクラスへのデータ定義と同様に、 ODQLを用いてクラス内に定義します。 例えば、Customerクラスに新たなオブジェクトを生成するaddCustomerという手 続きを定義したい場合は、クラス定義ファイルを以下の ように記述します \begin{verbatim} defineClass Customer super:Composite description:"Class Customer " { instance: Integer customerNo; String customerName; String customerAddress; Void addCustomer(Integer i,String n,String a); /*手続き属性*/ }; \end{verbatim} Void\hspace*{0.5cm}addCustomer(Integer i,String n,String a)が手続き属性の定義になりま す。ここでは手続きaddCustomerは引数としてi,n,aの3つを受け取り、値を返 さない(void型)ものとして定義されています。 \subsubsection{手続きのコーディング} クラスに定義した手続きが、実際に存在しないことには意味がありません。とい うわけで、定義した手続き属性を作成します。 addCustomerの内容は以下のようになっています \begin{verbatim} defineProcedure Void Customer::instance:addCustomer(Integer i,String n,String a) language :"c" { $defaultCF moritaCF; $Customer Cu; $Customer.new(customerNo:= i,customerName:= n,customerAddress := a); $return; }; \end{verbatim} defineProcedureというODQLコマンドが、手続きの内容を定義するコマンドで、 それ以降が実際の処理内容になります。ここでは、手続きの引数として与えられ た値を受け取り、その値をnew()に渡すことで新たなオブジェクトを生成してい ます。 このファイルをexecFile コマンドで実行することにより、手続きaddCustomerの 処理内容がクラスCustomerに定義されます \subsubsection{コンパイル} 定義した手続きはコンパイルしないと使えません。 コンパイルにはcompileProcedureを使います compileProcedureはクラス単位でのコンパイルを行います。 例えばクラスCustomerにaddCustomer,deleteCustomerという2つの手続きが定義 されている時、compileProcedure Customer;と入力することでこの2つのコンパイルが行われます。 \subsubsection{手続き使用} コンパイルが通れば、無事に手続きが使用出来るようになります Customerに定義したaddCustomerを使用し、新たなデータを登録するときにはイ ンタプリタ上で以下のように入力します \begin{verbatim} Customer cu; cu.addCustomer(1,morita,Fukuoka) \end{verbatim} すると、customerNoが1、customerNameがmorita、customerAddressがFukuokaという 新たなCustomerオブジェクトが生成されるはずです。 \section{制約の実現} 先ほどあげた制約は、基本的にリレーショナルデータベースについてのものなの で、オブジェクトデータベースであるJasmineでこれらの制約を実現するために は多少の工夫が必要になります。 \subsection{必須制約の実現} 必須制約をJasmineで実現するには、データ定義の際に「mandatory」という記述 を付け加えます。 たとえば、CommodityのCommodityNoに必須制約を適用したい場合は 定義ファイルにおいて \begin{verbatim} instance: Integer commodityNo mandatory:; \end{verbatim} のように、書けばOKです。こうしておけば、CommodityNoに何も値が指定されなかっ たときに、データベース側がエラーを通知してくるようになります。 \subsection{一意制約の実現} 一意制約も、必須制約と同じようにデータ定義ファイルに宣言を追加することで 実現します。 一意制約を適用したい場合は \begin{verbatim} instance: Integer commodityNo unique:; \end{verbatim} のようにします。 必須制約と一意制約は、同時に適用させることも可能です \begin{verbatim} instance: Integer commodityNo unique: mandatory:; \end{verbatim} これにより、CommodityNoは必須であり一意である、まさに主キーとしての性質 を持つことが可能になります。 \subsection{ドメイン制約の実現} ドメイン制約を実現するためには、手続き属性を使用します 以下に示した、Commodityにデータを追加する手続きの中でに、unitPriceが1円 以上というドメイン制約を実現するためのコードが記述してあります。 \begin{verbatim} defineProcedure Void Commodity::instance:addCommodity(Integer i,String n,Integer u,String a ) language :"c" { $defaultCF moritaCF; /*Domain制約*/ /*u(unitPrice<1なら、エラーを返す)*/ $if(u<1) { odbGenerateUserMtdError("moritaCF","Commodity","addCommodity","Domain error wrong unitPrice"); ODB_ERRORRETURN(-10); }; /*それ以外なら、データを追加*/ $Commodity.new(commodityNo:= i,commodityName:= n,unitPrice := u,commodityAddress := a); $return; }; \end{verbatim} この手続きでは、引数として受け取った追加するunitPriceの値uが1より小さい場合に エラーを発生させ、それ以外の場合はデータの追加を行います。 このような手続きを使用することで、データ追加 の際にドメイン制約を常に満たすように監視するということができるようになります。 \subsection{参照制約の実現} 参照制約の実現にも、手続き属性を使用します。 例えば以下の手続きでは,指定されたCustomerNoを持つオブジェクトを削除し、 そのオブジェクトを参照していたSalesのオブジェクトを削除することで参照制 約を満たします。 \begin{verbatim} defineProcedure Void Customer::instance:deleteCustomer(Integer dn) language :"c" { $defaultCF moritaCF; $Bag cus; $Customer cu; $Bag sas; $Sales sa; $cus = Customer from Customer where Customer.customerNo == dn; /*入力されたCustomerNoのオブジェクトが存在しなければ、エラーを返す*/ $if (cus.count() == 0){ odbGenerateUserMtdError("moritaCF","Customer","deleteCustomer","customerNo NOT FOUND"); ODB_ERRORRETURN(-10); }; /*オブジェクトが存在すれば、それを削除*/ $cu = Customer from Customer where Customer.customerNo == dn; $cu.delete(); /*削除されたオブジェクトを参照していたSalesの削除*/ $sas = Sales from Sales where Sales.soldCommodityNo == dn; $if (sas.count() == 1){ $sa = Sales from Sales where Sales.soldCommodityNo == dn; $sa.delete(); }; $return; }; \end{verbatim} \section{課題} 以下の手順に従って、各制約を持つテーブルを作成し、実際に手続きを使用して みましょう。 \begin{enumerate} \item 定義ファイルを持ってくる \begin{verbatim} cp -r /u/morita/DBfile ~ \end{verbatim} \item ファイルの修正 \begin{verbatim} cd ~/DBfile DBfile にあるadd******.odql,delete******.odql(6つ)を エディタで開き、defaultcf moritaCF;のmoritaCFを自分のCFに修正 \end{verbatim} \item インタプリタ起動 \begin{verbatim} ssh jasmine cd ~/DBfile bash source /jasmine/jasmine2/.profile codqlie \end{verbatim} \item CFの変更 \begin{verbatim} jasmine(systemCF) > defaultCF xxxxCF;(自分のCFに変更) \end{verbatim} \item クラス定義 \begin{verbatim} jasmine(xxxxCF) > execFile setClasses.odql; \end{verbatim} \item クラス構築 \begin{verbatim} jasmine(xxxxCF) > buildClass Customer Commodity Sales; \end{verbatim} \item 手続き定義 各制約を含んだデータ追加、削除の手続きを定義する。 \begin{verbatim} jasmine(xxxxCF) > execFile addCustomer.odql; jasmine(xxxxCF) > execFile deleteCustomer.odql; jasmine(xxxxCF) > execFile addCommodity.odql; jasmine(xxxxCF) > execFile deleteCommodity.odql; jasmine(xxxxCF) > execFile addSales.odql; jasmine(xxxxCF) > execFile deleteSales.odql; \end{verbatim} \item コンパイル \begin{verbatim} compileProcedure Customer Commodity Sales; \end{verbatim} \item 使用例 \begin{verbatim} Commodityの追加、削除 jasmine(moritaCF) > Commodity co; jasmine(moritaCF) > Bag cc; jasmine(moritaCF) > co.addCommodity(1,"video",19000); jasmine(moritaCF) > co.addCommodity(2,"TV",35000); jasmine(moritaCF) > cc = Commodity from Commodity; jasmine(moritaCF) > cc.print(); Bag{ moritaCF::Commodity::2 { commodityNo = 1, commodityName = "video", unitPrice = 19000 }, moritaCF::Commodity::3 { commodityNo = 2, commodityName = "TV", unitPrice = 35000 } } jasmine(moritaCF) > co.deleteCommodity(1); jasmine(moritaCF) > cc.print(); Bag{ <<>>, moritaCF::Commodity::3 { commodityNo = 2, commodityName = "TV", unitPrice = 35000 } } \end{verbatim} 手続きの引数になにを入れたらいいか分からないときは 手続きのソースを見るか、 インタプリタ上でクラス名.print()とするとクラスの定義内容が見られるので そこで調べましょう。 \end{enumerate} \end{document}