CakePHP5入門【CakePHP5基礎編⑥】データベース操作②
B美
A子
C菜
B美
SELECT 項目名列挙 | *
FROM 表名列挙 [WHERE 条件1] [GROUP BY 項目名列挙] [HAVING 条件2] [ORDER BY 項目名列挙] |
というのが基本形ね
(「| *」は「または*」という意味)
C菜
B美
(もちろん一つだけでもOK)
A子
B美
あ、「HAVING」句だけは単独で使えないからね
(必ず「GROUP BY」句と併用すること)
A子
B美
・「WHERE」句 … グループ化を行う「前」の条件指定
・「HAVING」句 … グループ化を行った「後」の条件指定
…ってわけ
C菜
B美
まずは…
SELECT id,remarks,counter
FROM counters WHERE id >= 5; |
を実行してみてね
C菜
mysql -u root -p testdb[Enter] |
を実行してからですよね~?
B美
↓
A子
ふむ、なるほど~
「SELECT」のあとに項目名を列挙すると、その列の値だけが表示されるのね
あと「WHERE」の条件として「id」が5以上って指定してるから、1~4の「id」のレコードが消えてるのか…
↓
B美
必要な列(項目)のみ抽出することを「射影」、条件に合った行(レコード)のみ抽出することを「選択」と呼びます
ちなみに、一行で打ち込んでも良いし、途中で改行してもOKよ
(SQLでは「;」を入力して[Enter]キーを押さない限り、命令は実行されないから…)
あと、分かりやすいように「句」を大文字で記述してるけど、(前にも言った通り)SQLでは大文字と小文字を区別しないからね
(普通は全て「小文字」で打ち込みます…面倒くさいので(笑))
それじゃ、次は「GROUP BY」句を使ってみましょう
SELECT remarks,sum(counter)
FROM counters WHERE id >= 5 GROUP BY remarks; |
C菜
つまり、sum(counter)は「counter」という項目の値を合計する関数だと思うです~
B美
それらを集合関数と呼んでるんだけど、ほかにも「avg()」「min()」「max()」「count()」なんかがあるわよ
・avg() … 平均値
・min() … 最小値
・max() … 最大値
・count() … レコード数
ちなみに、countだけはちょっと特殊なやつで、検索結果のレコード数(件数)を取得するの
(なので、カッコ内には項目名を書かずに「count(*)」という形で用いることが多いわね)
A子
B美
それこそがさっき言ったグループ化「後」の条件指定なのよ
だって、「remarks」の値でグループ化しないと、「sum(counter)」は計算できないでしょ?
んじゃ、さっきのSQL文に「HAVING」句を追加してみてね
SELECT remarks,sum(counter)
FROM counters WHERE id >= 5 GROUP BY remarks HAVING sum(counter) <= 100; |
C菜
あ、もしも「WHERE」句を削除しちゃったらどうなるんでしょうか~?
A子
うーん、「WHERE」条件と「HAVING」条件の違いが(なんとなく)分かったよ
B美
SELECT id,remarks,counter
FROM counters ORDER BY counter DESC; |
を実行してみてね
C菜
これって「DESC」を付けてるからでしょうか~?
B美
末尾に「ASC」を付けるか、何も付けなければ「昇順」ソート
「DESC」を付ければ「降順」ソートになるのよ
C菜
A子
何それ…?
B美
その通りよ
「ascending order」が「昇順」
「descending order」が「降順」
…って意味ね
だから「ASC」と「DESC」なのよ
A子
なんだか今回ってSQLばっかりやってるけど、「CakePHP」はどうなったのよ
B美
実はModelクラスのメソッド名が「select()」とか「where()」なのよね
C菜
$counters = $this->Counters->find()->where(['id' => 1])->first(); |
…って感じで~
(「CakePHP5入門【CakePHP5基礎編③】MVC」を参照)
A子
そっか、これって「idが1と等しいレコードを抽出」って意味かー
B美
ちなみに、検索結果って「連想配列」が「配列」化されたもの(複数行あるから)になるんだけど、結果が1レコードしかないのが明らかな場合って、末尾に「->first()」を付けることで「配列」化しないようにもできるの
A子
直接「$count['remarks']」や「$count['counter']」って書いてたもんね
C菜
言い換えれば、「first」メソッドを呼ばない場合、必ず「foreach」で処理するって考えておけば良いですかぁ~?
B美
検索結果の全レコードを取得するには「all」メソッドを呼ぶ必要があるのよ
(だから「firstメソッドを呼ばない場合」ではなく、「allメソッドを呼んだ場合」という表現が正確かな)
ちなみに、前回(CakePHP5入門【CakePHP5基礎編⑤】データベース操作①)、Viewファイルである「testapp/templates/Counters/index.php」の中身を見てなかったけど、実はこのように「foreach」を使ってたの
A子
foreachの書き方が以前に習ったのと違うんだけど?
B美
foreach ($counters as $counter) {
} |
って
foreach ($counters as $counter):
endforeach; |
という風に書くこともできるの
どっちを使っても構わないんだけど、私は上の書き方のほうが好みね
(だからPHPの文法編では、そっちを説明したってわけ)
C菜
「if」文や「while」文も同じですか~?
B美
波カッコを使わず、閉じるのに「endif」や「endwhile」を使う方法もあるわ
ただまぁ、コードを打ち込む量が増えるから、私は嫌いだけどね
A子
C菜
これって連想配列じゃなかったんですかぁ~?
B美
さっき「データベースの検索結果は連想配列の配列」って言ったけど、実は嘘っぱちだから…(苦笑)
A子
いったいどういうことよ(怒)
C菜
「->」を使ってるってことは、これってきっと「オブジェクト」のはずです~
B美
実はデータベースの検索結果って、「ResultSet」クラスのオブジェクトなのよ
ただ、さっきも言ったように「連想配列が配列化されたもの」のごとく取り扱えるってわけ
まぁ、項目へのアクセスは->でやったほうが文字数が少なくなるから良いんだけど…(苦笑)
A子
まぁ確かに「$counter['id']」よりは「$counter->id」のほうがシンプルで分かりやすいかも…
(キーボードを打つ量も減るし…)
B美
「testapp/src/Controller/CountersController.php」を開いて、次のように「search」メソッドを追記してね
public function search($id = 1)
{ $query = $this->Counters->find(); switch ($id) { case 1:$result = $query->all(); break; case 2:$result = $query->select(['id', 'remarks', 'counter'])->where(['id >=' => 5])->all(); break; case 3:$result = $query->select(['remarks', 'sum_counter' => $query->func()->sum('counter')])->where(['id >=' => 5])->group('remarks')->all(); break; case 4:$result = $query->select(['remarks', 'sum_counter' => $query->func()->sum('counter')])->where(['id >=' => 5])->group('remarks')->having(['sum_counter <=' => 100])->all(); break; case 5:$result = $query->select(['remarks', 'sum_counter' => $query->func()->sum('counter')])->group('remarks')->having(['sum_counter <=' => 100])->all(); break; case 6:$result = $query->select(['id', 'remarks', 'counter'])->order(['counter' => 'desc'])->all(); break; } $fields = array_keys($result->first()->toArray()); $this->set(compact('fields', 'result')); } |
C菜
「switch」って何なんでしょうか~?
A子
B美
switch (変数) {
case 値A:変数が左の値Aと等しいときの処理;break; case 値B:変数が左の値Bと等しいときの処理;break; case 値C:変数が左の値Cと等しいときの処理;break; case 値D:変数が左の値Dと等しいときの処理;break; case 値E:変数が左の値Eと等しいときの処理;break; case 値F:変数が左の値Fと等しいときの処理;break; } |
…ってことよ
A子
if elseif elseif …ってことでしょ?
B美
分岐の数が多いときは「switch」文のほうが見やすいからね
あ、一つ注意点があります
それぞれのcaseの最後には必ず「break;」を記述すること
でないと、「break;」が出現するまで「switch」ブロック内を全て実行しちゃうことになるからね
C菜
B美
「localhost/testapp/counters/search/1」のように、URLの末尾に「/数値」を付けるの
そうすると、「search」メソッドの引数「$id」に数値(上記の例ならば1)が渡るってわけ
ちなみに「= 1」ってのはデフォルト値で、「localhost/testapp/counters/search」のようにアクセスした場合は「$id」が「1」になります
A子
(デフォルト値は「null」だったけど…)
B美
「testapp/templates/Counters」ディレクトリ内に「search.php」というファイルを作ります
<table>
<?php foreach ($fields as $field) { ?> <th><?= h($field) ?></th> <?php } ?> <?php foreach ($result as $value) { ?> <tr> <?php foreach ($fields as $field) { ?> <td style="line-height: 10px;"><?= h($value->get($field)) ?></td> <?php } ?> </tr> <?php } ?> </table> |
A子
B美
実は「h()」って「htmlspecialchars()」と同じものなのよ
C菜
つまり、「サニタイジング」ってわけですね~
B美
それじゃ、実行確認していきましょう
まずはブラウザのURLに
「localhost/testapp/counters/search/1」
または 「localhost/testapp/counters/search」 |
A子
SELECT * FROM counters; |
ってことかー
コード的には
$query = $this->Counters->find();
$result = $query->all(); |
…ってことになるね
(さっき打ち込んだ「search」メソッドの内容から抽出すると…)
B美
よく分かってるじゃん
では、次は
「localhost/testapp/counters/search/2」 |
C菜
SELECT id,remarks,counter
FROM counters WHERE id >= 5; |
というSQLで~
コード的には
$query = $this->Counters->find();
$result = $query->select(['id', 'remarks', 'counter'])->where(['id >=' => 5])->all(); |
ですね~?
B美
さぁ、どんどんいくわよー
次は
「localhost/testapp/counters/search/3」 |
A子
(ただし「id」は5以上で)
SQLは
SELECT remarks,sum(counter)
FROM counters WHERE id >= 5 GROUP BY remarks; |
で、コードは
$query = $this->Counters->find();
$result = $query->select(['remarks', 'sum_counter' => $query->func()->sum('counter')])->where(['id >=' => 5])->group('remarks')->all(); |
…ってことか
「'sum_counter' => $query->func()->sum('counter')」の箇所だけが、少し(いや、かなり)ややこしい…(苦笑)
B美
んじゃ、次は
「localhost/testapp/counters/search/4」 |
C菜
SQLは
SELECT remarks,sum(counter)
FROM counters WHERE id >= 5 GROUP BY remarks HAVING sum(counter) <= 100; |
コードは
$query = $this->Counters->find();
$result = $query->select(['remarks', 'sum_counter' => $query->func()->sum('counter')])->where(['id >=' => 5])->group('remarks')->having(['sum_counter <=' => 100])->all(); |
です~
コードがすっごく長いです~(苦笑)
A子
B美
URLは
「localhost/testapp/counters/search/5」 |
A子
SQLは
SELECT remarks,sum(counter)
FROM counters GROUP BY remarks HAVING sum(counter) <= 100; |
で、コードは
$query = $this->Counters->find();
$result = $query->select(['remarks', 'sum_counter' => $query->func()->sum('counter')])->group('remarks')->all(); |
…ってことかー
B美
「localhost/testapp/counters/search/6」 |
C菜
SQLは
SELECT id,remarks,counter
FROM counters ORDER BY counter DESC; |
コードは
$query = $this->Counters->find();
$result = $query->select(['id', 'remarks', 'counter'])->order(['counter' => 'desc'])->all(); |
ですね~
B美
SQL文とPHPコードを並べると、なんとなく見えてきたでしょ?
A子
C菜
B美
余談だけど、「select」メソッドはあまり使わないわ
(集合関数を使いたいなら別だけど…)
A子
B美
つまり「射影」は必須じゃないってこと
あと、グループ化もできるだけ使わないように(使わなくて済むように)データベース設計するかな?
(個人的な意見だけど…)
C菜
「where」メソッドと「order」メソッドの使い方さえマスターしておけば大丈夫…ってことでしょうか~?
B美
そして、両方ともそれほど難しくはないでしょ?
A子
B美
ちなみに「where」メソッドに複数の条件を指定した場合、自動的にAND検索(~ かつ ~)になるからね
C菜
B美
「条件1または条件2」ってことになるわ
(条件1や条件2の個所も連想配列として記述)
例えば、「idが5以上」または「counterが10未満」という条件の場合
$query = $this->Counters->find();
$result = $query->where(['OR' => [['id >=' => 5], ['counter <' => 10]]])->all(); |
…って感じ
A子
B美
さて、それでは今回はここまで
次回は直接SQL文を実行する方法を解説します