CakePHP5入門【CakePHP5基礎編⑦】データベース操作③
B美
A子
C菜
あ、一つ質問なんですが、前回「結合」という言葉が出てきたのに、その説明が無かったです~
B美
例えば、「顧客」表(マスターテーブル)があって、その主キーである「顧客コード」が「売上」表(トランザクションテーブル)に存在するとしましょう
このときテーブルは次の二つ
【顧客】
顧客コード | 顧客名 | 顧客住所 | 顧客電話番号 |
---|---|---|---|
1001 | ○○商事 | 東京都港区… | 03-xxxx-xxxx |
1002 | △△商会 | 大阪府大阪市… | 06-xxxx-xxxx |
1003 | 株式会社×× | 福岡県北九州市… | 093-xxx-xxxx |
【売上】
伝票番号 | 売上日付 | 商品名 | 顧客コード |
---|---|---|---|
T123 | 2024/01/11 | X | 1001 |
T124 | 2024/01/11 | Y | 1003 |
T125 | 2024/01/12 | Z | 1001 |
T126 | 2024/01/13 | X | 1002 |
T127 | 2024/01/13 | Y | 1003 |
T128 | 2024/01/13 | Z | 1001 |
【注意点】
これって「1枚の伝票に1つの商品しか書けない」という制約下で作ってるんだけど、実際の伝票はそうじゃないからね
あと、商品についても「商品」表(マスターテーブル)を作るべきなんだけど、そうしてないから…
C菜
【売上一覧】
伝票番号 | 売上日付 | 商品名 | 顧客コード | 顧客名 | 顧客住所 | 顧客電話番号 |
---|---|---|---|---|---|---|
T123 | 2024/01/11 | X | 1001 | ○○商事 | 東京都港区… | 03-xxxx-xxxx |
T124 | 2024/01/11 | Y | 1003 | 株式会社×× | 福岡県北九州市… | 093-xxx-xxxx |
T125 | 2024/01/12 | Z | 1001 | ○○商事 | 東京都港区… | 03-xxxx-xxxx |
T126 | 2024/01/13 | X | 1002 | △△商会 | 大阪府大阪市… | 06-xxxx-xxxx |
T127 | 2024/01/13 | Y | 1003 | 株式会社×× | 福岡県北九州市… | 093-xxx-xxxx |
T128 | 2024/01/13 | Z | 1001 | ○○商事 | 東京都港区… | 03-xxxx-xxxx |
「顧客名」「顧客住所」「顧客電話番号」の値がめっちゃ重複してます~
A子
「売上一覧」表のように表が一つの場合、もしも引っ越しして住所が変わったりしたら大変なことになるのかー
(「顧客」表があれば、変更箇所は一ヶ所だけだもんね)
B美
適切に表(テーブル)を分割することを「正規化」と呼びます
そして「正規化」による適切な「データベース設計」こそが、データベースエンジニアの腕の見せ所でもあるってわけ
C菜
B美
まずは「売上」表から商品名が「Z」であるレコードを検索して、そのレコードの顧客コードが「1001」であることを知るわけ
次に、その値「1001」を使って「顧客」表を検索すれば、顧客名が「○○商事」であると分かるわよね?
A子
B美
「副問い合わせ」という方法を使えば、一度で(一つのSQL文で)検索できるわよ
C菜
B美
上でC菜が示した「売上一覧」表(めっちゃ重複データがあるやつ)があるわよね?
あれって、私が書いた「顧客」表と「売上」表の二つを「結合」したものとも言えるわけ
その結合した表を商品名「Z」で検索すれば、一発で「○○商事」という顧客名が得られるでしょ?
A子
あ、さっきは(話の腰を折るのもなんだから)質問を控えてたんだけどさ
「マスターテーブル」とか「トランザクションテーブル」って何?
B美
「売上」表や「取引」表のように日々データが増加していくテーブルが「トランザクションテーブル」よ
【参照頻度:高 更新頻度:低】なのが「マスターテーブル」
【参照頻度:低 更新頻度:高】なのが「トランザクションテーブル」とも言えるかな
A子
なんとなく分かったわ
C菜
本題に戻りますけど、「CakePHP」ではテーブルを「結合」できないんですか~?
B美
ただ、その方法は二種類あるの
一つは、まさに「join」メソッドを使う方法
もう一つは、「contain」メソッドを使う方法
(「contain」メソッドを使用する場合は、テーブルのクラス内で「belongsTo」メソッドや「hasMany」メソッドを記述する必要あり)
A子
B美
「join」を使う場合は、Controller内に面倒な記述をしなきゃいけないのに対して
「contain」のほうはめっちゃシンプルに書けるの
(面倒なことは、テーブルのクラス内に記述すれば良いじゃん…って考え方ね)
C菜
B美
SQLの「CREATE TABLE」文を実行する際、主キー(PRIMARY KEY)を設定するのは必須なんだけど、外部キー(FOREIGN KEY)を設定することもできるの
外部キーを設定したテーブルに対して「bake model」コマンドを使ってModelクラス(テーブルのクラス)を作ると、「結合」に必要な「belongsTo」メソッドや「hasMany」メソッドの記述を自動生成してくれるのよね
(そうすればController内で、シンプルに「contain」メソッドを呼ぶだけで済むってわけ…超簡単!)
A子
言い換えれば、絶対に重複しない値を格納してる項目…ってことよね?
ただ、「外部キー」ってのが分からん!
いったい何なのよ?
B美
…と定義されているわね
A子
さっぱり分からん!
B美
ある表(「顧客」表)の主キー(「顧客コード」)が他の表(「売上」表)の主キー(「伝票番号」)以外の項目として存在するとき、他の表(「売上」表)の中のその項目(「顧客コード」)を外部キーと呼ぶ
…ってこと
要するに、「顧客」表と「売上」表を連携させるためには両方の表に「顧客コード」が必要じゃん?
そのとき「顧客コード」って「顧客」表では「主キー」、「売上」表では「外部キー」と呼ばれるってだけの話よ
C菜
B美
ちなみに「結合」は、「主キー」項目と「外部キー」項目を「=」で結ぶことで行われるわ
(これはSQL文の話…「CakePHP」では先ほど話に出た「belongsTo」メソッドを使います)
A子
具体的なコードが無いと、いまいち分からん!
B美
とりあえず現時点においては省略します
(先々コラム的な話として、やるかもしれないけど…)
C菜
B美
さて、それじゃ今回の授業を始めます
まずは「MATE端末」を開いて次のように入力してね
cd html/testapp[Enter]
bin/cake bake controller SqlTest[Enter] |
C菜
B美
(要するに、下記の状態にします)
B美
public function index()
{ $connection = ConnectionManager::get('default'); $sql = "SELECT id,remarks,counter FROM counters"; $results = $connection->execute($sql)->fetchAll('assoc'); $this->set(compact('results')); } |
あと重要なのが、上のほうにある「namespace …」の下に次の一文を記述すること!
(これって「index」メソッド内で使っている「ConnectionManager」の場所はここですよー…って意味ね)
use Cake\Datasource\ConnectionManager; |
(表示上、エン記号がバックスラッシュ記号に変わっていますが、同じ文字です)
C菜
B美
「testapp/templates」を開いて「SqlTest」ディレクトリを新規作成しましょう
もちろん、その中に「index.php」も作ってね
B美
(頑張って入力してね…コピペでも良いけど)
<style>
table th, td { line-height: 10px; } </style> <table> <thead> <tr> <th>id</th> <th>remarks</th> <th>counter</th> </tr> </thead> <tbody> <?php foreach ($results as $value) { ?> <tr> <td><?= $value['id'] ?></td> <td><?= $value['remarks'] ?></td> <td><?= $value['counter'] ?></td> </tr> <?php } ?> </tbody> </table> |
注意点だけど、「$value->id」という書き方はできないからね
B美
URLは「localhost/testapp/sqlTest」よ
C菜
まさか検索結果がオブジェクトじゃないとか~?
B美
おそらくだけど、検索結果は「連想配列」の「配列」なんだと思う
まぁ良いじゃない
気にしない、気にしない(笑)
A子
そんないいかげんなことで良いの?
B美
(文系プログラマも多いわよ)
てか、プログラムなんて動きゃ良いのよ、動きゃ!
C菜
B美
さっきの「index」メソッドを次のように書き換えてね
(赤と緑の色で示したところが変更した箇所ね)
public function index()
{ $connection = ConnectionManager::get('default'); $sql =<<<EOF SELECT id,remarks, CASE WHEN counter = 0 THEN 'ゼロ' WHEN counter >= 1 AND counter <= 10 THEN '少' WHEN counter >= 11 AND counter <= 99 THEN '中' WHEN counter >= 100 THEN '多' END AS counter FROM counters; EOF; $results = $connection->execute($sql)->fetchAll('assoc'); $this->set(compact('results')); } |
↓
C菜
これってデータベース自体が更新されちゃったんですかぁ~?
B美
View側には(「少」とか「多」のような)文字列が渡されたけど、データベースのデータ自体は更新されてないの
「counter」の値によって出力されるデータを変更することができたのは、SQLの「CASE」式を使ったからよ
A子
B美
A子もなかなかやるじゃん
まぁ、複雑なSQL文を書かないと「SQL文の直接実行」という行為を理解しづらいからね
(別に「CASE」式を覚えなくても良いわよ…単なるサンプルなので(笑))
ちなみに「AS ○○」というのは、その項目に「○○」という別名(エイリアス)を付けます…って意味よ
(Viewファイルを修正したくなかったから、同じ「counter」という名前にしただけ)
C菜
「$sql =<<<EOF」って何なんですか~?
B美
複数行の文字列を変数に代入したいってときに使うのよ
$変数名 =<<<文字列
・・・ (複数行) ・・・ 文字列; |
という形式ね
A子
必ずその名前にするの?
B美
何でも良い(「ABC」でもOK)んだけど、「EOF」「EOD」「EOT」なんかを使う人が多いみたい
(私は「EOF」ばっか使うけど…)
あと、これは本当にどうでもいい話なんだけど、それらの文字列って「End Of File」「End Of Document」「End Of Text」の頭文字を並べたものだからね
(てか、意味的に「EOF」はおかしいんだけど…(笑))
C菜
B美
あ、「ヒアドキュメント」を使うときに注意しなきゃいけないことが一つ
ダブルクォートで挟まれた文字列中に変数や配列の名前を書くと「変数展開」される…って話、憶えてる?
C菜
ちょっと面倒なやつです~
B美
A子
B美
その方法は超簡単で、最初の「文字列」をシングルクォートで囲めば良い('EOF')だけなの
具体的には
$sql =<<<'EOF'
SELECT id,remarks, CASE WHEN counter = 0 THEN 'ゼロ' WHEN counter >= 1 AND counter <= 10 THEN '少' WHEN counter >= 11 AND counter <= 99 THEN '中' WHEN counter >= 100 THEN '多' END AS counter FROM counters; EOF; |
…ってことね
(最後の「文字列」部分(EOF)については、シングルクォートを付けないように!)
C菜
そういうときは「変数展開」されたほうが便利なんじゃないですか~?
B美
こんな感じでSQL文の中に変数の値を埋め込むことは、よくやるかな
$value = 100;
$sql =<<<EOF SELECT id,remarks,counter FROM counters; WHERE counter > {$value}; EOF; |
A子
そういえば、さっきの「CASE」式ってさ
(SQLじゃなくて)Modelのクラスを使って、実現できないの?
B美
なので、例となるコードは省略
(というか、私が書きたくない(笑))
C菜
B美
私だったら、そうするかな
(まぁ、人それぞれお好きなように…って感じだけど)
プログラムって「正解は一つじゃない」からね
いえ「プログラマの数だけ正解がある」と言っても過言ではないわ
A子
さっき言ってた「プログラムなんて動きゃ良い」ってのは、そういうことか…
B美
「CakePHP」の基礎的な話はいったん終わって、次回からは実用的な「Webアプリケーションシステム」を作っていきます
C菜
楽しみです~
A子
データベーステーブルに対する「レコードの追加」「レコードの更新」「レコードの削除」なんかをやってないような気が…
B美
基礎的な話ばかりだと面白くないからね(苦笑)