データベースとWebアプリケーション開発の基礎

概要

本教材は、データベースの基礎からWebアプリケーション開発までを、15の演習(演習1から演習15)を通じて段階的に学習するためのガイドである。各演習では、SQLによるデータ操作、HTMLによるWebページ作成、PythonとFlaskを用いたバックエンド開発のいずれかを取り上げる。すべての演習はWindowsのコンピュータ上で完結する。

対象者

学習内容

使用ツール

目次

【サイト内の関連ページ】

本教材の読み方と前提

学習を始める前に、教材全体に共通する次の3点を理解しておく。

サンプルコードには先取りの構文が登場する

各演習のサンプルコードには、後の演習で学ぶSQLの構文があらかじめ登場することがある。たとえば演習3のサンプルには、テーブルを結合するJOINや、列に別名を付けるASが含まれるが、これらを本格的に扱うのは演習6である。先に出てきた構文は、その時点では「この書き方でデータを取り出している」という程度の理解でよく、該当する演習で改めて意味を確認する。

本教材で使うSQLiteの位置づけ

本教材の演習では、ファイル型データベースであるSQLiteを使用する。SQLiteはファイル1つでデータを管理でき、1人で学習・開発するのに適している。概要で述べる「複数ユーザによる同時アクセス」は、データベース管理システム(DBMS)が一般に備える利点の説明である。SQLiteは多人数が同時に書き込む用途には向かないが、本教材の学習目的には十分である。

データベースファイルは自動生成・削除で作り直せる

演習12以降のPythonプログラムが使うデータベースファイル(shop.dbsales.db)は、プログラムを実行するとmain.pyと同じフォルダに自動で作成される。サンプルデータを変更する、または最初からやり直す場合は、このファイルを削除してからプログラムを再実行する。削除すると空の状態から作り直され、演習によってはサンプルデータも再投入される(この動作は各演習内で個別に説明する)。

データベースの基礎知識と学習目標

データベースとは

データベース(DB)は組織的に管理されたデータの集合である。データベース管理システム(DBMS)はデータベースを管理するソフトウェアであり、複数のユーザが同時にデータへアクセスできる環境を提供する。

学習目標

3つの学習フェーズ

基礎学習フェーズ(演習1から演習10)

実践学習フェーズ(演習11から演習13)

応用学習フェーズ(演習14から演習15)

演習環境の準備(Windows)

この演習ではPythonとWebブラウザを使用する。以下の準備を演習前に完了する。すべてWindows上の操作であり、Linuxなどの知識は不要である。

Python 3.12 のインストール

Pythonのインストールを行い、Pythonのプログラムを実行する環境を整える。扱う環境は、Windows搭載パソコンである。金子研究室では、Python 3.12.10を推奨する。

[Windows での Python 3.12 のインストール手順を見るには、ここをクリック]

Windows での Python 3.12 のインストール

以下のいずれかの方法でPython 3.12をインストールする。Pythonがインストール済みの場合、この手順は不要である。

方法 1:winget によるインストール

インストールコマンドの実行方法

管理者権限コマンドプロンプトを起動する(手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして、コマンド全体をコマンドプロンプトにコピー&ペーストする。

--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動するとPATHが反映される。

REM Python 3.12 をシステム領域にインストール
winget install --id Python.Python.3.12 -e --scope machine --silent --accept-source-agreements --accept-package-agreements --override "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_pip=1 Include_launcher=1 InstallLauncherAllUsers=1 TargetDir=\"C:\Program Files\Python312\""

REM Python と Scripts を PATH 先頭に追加
powershell -NoProfile -Command "$p='C:\Program Files\Python312'; $s=\"$p\Scripts\"; $c=[Environment]::GetEnvironmentVariable('Path','Machine'); if((Test-Path $p) -and (';'+$c+';' -notlike \"*;$p;*\") -and (';'+$c+';' -notlike \"*;$s;*\")){[Environment]::SetEnvironmentVariable('Path',\"$p;$s;$c\",'Machine')}"

方法 2:インストーラーによるインストール

  1. Python公式サイト(https://www.python.org/downloads/)にアクセスし、「Download Python 3.x.x」ボタンからWindows用インストーラーをダウンロードする。
  2. ダウンロードしたインストーラーを実行する。
  3. 初期画面の下部に表示される「Add python.exe to PATH」にチェックを入れてから「Customize installation」を選択する。このチェックを入れ忘れると、コマンドプロンプトから python コマンドを実行できない。
  4. 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。

インストールの確認

コマンドプロンプトで以下を実行する。

python --version

バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。「'python' は、内部コマンドまたは外部コマンドとして認識されていません。」と表示される場合は、インストールが正常に完了していない。

Python の開発環境 Visual Studio Code のインストールと Python 用の設定

Python の開発環境Visual Studio Code(プログラムを編集するソフトウェア。以下、VS Code)を整える。

[Windows での Visual Studio Code のインストールと Python 用の設定手順を見るには、ここをクリック]

Windows での Visual Studio Code のインストールと Python 用の設定手順

1. VS Code と拡張機能のインストール

以下のコマンドにより,既存の VS Code を削除し,全ユーザー共有の設定で再インストールしたうえで,拡張機能(VS Code に機能を追加するソフトウェア)をまとめて導入する.

インストールコマンドの実行方法

管理者権限コマンドプロンプトを起動する(手順:Windows キーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。そして,コマンド全体をコマンドプロンプトにコピー&ペーストする。

インストールコマンド


REM ============================================================
REM Microsoft Visual Studio Code
REM ============================================================
winget uninstall -e --id Microsoft.VisualStudioCode --silent --disable-interactivity --accept-source-agreements
rmdir /s /q C:\ProgramData\vscode-extensions 2>nul
rmdir /s /q "%APPDATA%\Code" 2>nul
rmdir /s /q "%USERPROFILE%\.vscode" 2>nul
rmdir /s /q "%LOCALAPPDATA%\Microsoft\vscode-update" 2>nul

REM VS Code をシステム領域に新規インストール
winget install --scope machine --id Microsoft.VisualStudioCode -e --silent --accept-source-agreements --accept-package-agreements

REM 全ユーザー共有の拡張機能フォルダ
mkdir C:\ProgramData\vscode-extensions 2>nul
icacls "C:\ProgramData\vscode-extensions" /grant "Everyone:(OI)(CI)M" /T

REM スタートメニューのショートカットを --extensions-dir 付きで再作成
rmdir /s /q "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code" 2>nul
del "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk" 2>nul
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); $lnk.TargetPath='C:\Program Files\Microsoft VS Code\Code.exe'; $lnk.Arguments='--extensions-dir \"C:\ProgramData\vscode-extensions\"'; $lnk.Save()"
REM ショートカットの検証
powershell -NoProfile -Command "$s=New-Object -ComObject WScript.Shell; $lnk=$s.CreateShortcut('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio Code.lnk'); Write-Host 'TargetPath:' $lnk.TargetPath; Write-Host 'Arguments:' $lnk.Arguments"

REM ファイル / フォルダ右クリックの「Code で開く」を登録
reg add "HKLM\SOFTWARE\Classes\*\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%1\"" /f
reg add "HKLM\SOFTWARE\Classes\Directory\Background\shell\VSCode\command" /ve /d "\"C:\Program Files\Microsoft VS Code\Code.exe\" --extensions-dir \"C:\ProgramData\vscode-extensions\" \"%V\"" /f

REM --extensions-dir 付きで起動する code.cmd ラッパを作成
REM (%* を echo で書くと対話的 cmd で失われるため、PowerShell で [char]37+'*' を書き出す)
powershell -NoProfile -Command "$pct=[char]37; $q=[char]34; $c='@echo off'+[char]13+[char]10+$q+'C:\Program Files\Microsoft VS Code\bin\code.cmd'+$q+' --extensions-dir '+$q+'C:\ProgramData\vscode-extensions'+$q+' '+$pct+'*'+[char]13+[char]10; [IO.File]::WriteAllText('C:\ProgramData\vscode-extensions\vscode.cmd',$c,[Text.Encoding]::ASCII)"

REM 拡張機能のインストール
set "CODE=C:\Program Files\Microsoft VS Code\bin\code.cmd"
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --uninstall-extension GitHub.copilot-chat
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.python
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.vscode-pylance
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension ms-python.debugpy
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension MS-CEINTL.vscode-language-pack-ja
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension saoudrizwan.claude-dev
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension rust-lang.rust-analyzer
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension tamasfe.even-better-toml
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension anthropic.claude-code
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --install-extension almenon.arepl
"%CODE%" --extensions-dir "C:\ProgramData\vscode-extensions" --list-extensions --show-versions
echo === セットアップ完了 ===

2. Python インタプリタの選択

同一マシンに複数の Python がインストールされている場合,VS Code で使用する Python 本体(インタプリタ:Python プログラムを解釈・実行するソフトウェア)を選択する必要がある.

  1. コマンドパレット(コマンド名で機能を呼び出す VS Code の入力欄)を開く(Ctrl+Shift+P
  2. Python: Select Interpreter と入力する
  3. 表示される一覧から,使用する Python(例:C:\Program Files\Python312\python.exe)を選択する.

Python プログラム実行手順

[Windows での Python プログラム実行手順を見るには、ここをクリック]

Windows での Python 実行手順(Visual Studio Codeを使用)

プログラムファイルの作成と保存

  1. 左サイドバーの「エクスプローラー」アイコン(Ctrl+Shift+E)をクリックする
  2. 「NO FOLDER OPENED」(作業対象フォルダが未選択の状態)と表示される場合は,「Open Folder」をクリックし,プログラムを保存するフォルダを選択する

    続いて「フォルダを信用するか」を確認する画面(フォルダ内のコードを実行してよいか確認する VS Code の仕組み)が表示されるので,チェックして Yes を選択する

  3. フォルダ名の右側に表示される「新しいファイル」アイコンをクリックする
  4. ファイル名(例:aitask.py.ファイル名は何でも良い)を入力し Enter を押す.拡張子は .py(Python ファイルを示す拡張子)とする
  5. 実行したいコードを選択し,Ctrl+C でコピーする.VS Code のエディタ領域に Ctrl+V で貼り付ける
  6. Ctrl+S で保存する

プログラムの実行

  1. エディタ右上の三角形「▷」アイコン(Run Python File:現在開いている Python ファイルを実行するボタン)をクリックする.または,エディタ上で右クリックし「ターミナルで Python ファイルを実行」を選択する
  2. VS Code 下部のターミナル(コマンドの入出力を表示する画面)に,実行結果(print 関数の出力等)が表示される
  3. tkinter(Python 標準の GUI ライブラリ)のファイル選択ダイアログを使うプログラムを実行した場合は,ダイアログが開くので対象画像を選択する
  4. VS Code 下部のターミナルで実行結果を確認する.OpenCV ウィンドウ(OpenCV が画像を表示するために開く専用ウィンドウ)が開いた場合はそちらも確認する.OpenCV ウィンドウは,マウスクリックでウィンドウをアクティブ(操作対象の状態)にしてからキーを押すと終了する

必要なライブラリのインストール

管理者権限コマンドプロンプトを起動する (手順:Windowsキーまたはスタートメニュー → cmd と入力 → 右クリック → 「管理者として実行」)。

次のコマンドを実行

python -m pip install -U --no-user flask

インストールの確認として、以下を実行する。

python -c "import flask; print(flask.__version__)"

バージョン番号(例:3.1.3 以降)が表示されればインストールは成功である。SQLite を扱う sqlite3 モジュールは Python に標準同梱されているため、別途インストールは不要である。

OneCompilerの動作確認

演習1から演習9、および演習15では、ブラウザ上でSQLを実行できるオンラインエディタであるOneCompilerを使用する。以下の手順で事前に動作を確認する。

  1. WebブラウザでOneCompilerのSQLiteページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。
  2. エディタにSELECT 1;と入力し、「Run」をクリックする。
  3. 出力(Output)セクションに結果が表示されれば動作確認は完了である。

OneCompilerにアクセスできない場合や応答がない場合は、時間を空けて再度アクセスする。それでも利用できない場合は、演習12以降で使用するローカルのSQLite(Python標準のsqlite3)でも同じSQLを実行できる。

ツールの使い方

OneCompilerでの実行手順(演習1から演習9、演習15で使用)

  1. OneCompilerのSQLiteページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。
  2. エディタに各演習のSQLコード(テーブル作成、データ挿入、クエリ)をまとめて入力する。
  3. 「Run」をクリックして結果を確認する。
注意事項:新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

Python + Flask のローカル実行手順(演習11から演習14で使用)

演習11から演習14のWebアプリケーションは、Windowsにインストールした Python と Flask で実行する。手順は以下のとおりである。

  1. 作業用フォルダを作成する(例:ドキュメント\flask_lessons)。
  2. 各演習のコードを main.py として保存する。テンプレートが必要な演習(演習12から演習14)は、main.py と同じ場所に templates という名前のフォルダを作成し、その中に指定の HTML ファイルを保存する。
  3. コマンドプロンプトを開き、cd コマンドで作業用フォルダに移動する(例:cd %USERPROFILE%\Documents\flask_lessons)。
  4. python main.py を実行する。
  5. Webブラウザで http://localhost:5000 にアクセスして結果を確認する。
  6. サーバを停止するには、コマンドプロンプトで Ctrl + C を押す。
フォルダ構成の例(演習12):
flask_lesson12\
├── main.py
└── templates\
    └── products.html
Windows特有の注意:初回実行時に「Windows セキュリティの重要な警告」(ファイアウォール)が表示された場合は、「アクセスを許可する」を選択する。ローカル開発用サーバ(localhost)への接続を許可するための確認である。
演習ごとのフォルダ分けについて:演習11から演習14はそれぞれ独立したプログラムであり、演習12と演習13はともにshop.dbという名前のデータベースファイルを使用するが、テーブルの定義が異なる。したがって、演習ごとに別のフォルダ(例:flask_lesson11flask_lesson12flask_lesson13flask_lesson14)を作成して作業する。同じフォルダを使い回すと、前の演習で作成したデータベースファイルが残ったまま新しい演習のプログラムが実行され、意図しない結果になる場合がある。

演習1.データベースの基本

この演習で学ぶこと:データベースの役割、テーブルの構造、CREATE TABLE文とINSERT文の基本

手順:Webブラウザで OneCompiler の SQLite ページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。エディタに以下のSQLをすべて貼り付け、「Run」をクリックする。出力(Output)セクションに表示される結果を確認する。なお、新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

-- 商品テーブルの作成
-- CREATE TABLE文でテーブルの構造を定義する
CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,  -- 主キー:各行を一意に識別
    name TEXT NOT NULL,              -- 商品名(必須)
    price INTEGER NOT NULL,          -- 価格(必須)
    stock INTEGER NOT NULL DEFAULT 0 -- 在庫数(デフォルト値0)
);

-- INSERT文でデータを挿入する
INSERT INTO products (product_id, name, price, stock) VALUES
(1, 'ノートパソコン', 89800, 15),
(2, 'ワイヤレスマウス', 2980, 50),
(3, 'キーボード', 5800, 30),
(4, 'モニター', 24800, 20),
(5, 'Webカメラ', 7800, 25);

-- 全データを取得
SELECT * FROM products;

-- 特定の列のみ取得
SELECT name, price FROM products;

ヒント:SELECT ** はすべての列を表す。列名を並べると、その列だけを取得する。

考察ポイント:SELECT *SELECT name, price で出力される列の違いを確認する。stock を指定しなかった行で、DEFAULT 0 がどう反映されているかを確認する。

演習2.リレーショナルデータベースとSQL入門

この演習で学ぶこと:リレーショナルデータベースの概念、SELECT文とINSERT文の使い方

手順:Webブラウザで OneCompiler の SQLite ページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。エディタに以下のSQLをすべて貼り付け、「Run」をクリックする。出力(Output)セクションに表示される結果を確認する。なお、新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price INTEGER NOT NULL,
    stock INTEGER NOT NULL DEFAULT 0
);

INSERT INTO products (product_id, name, price, stock) VALUES
(1, 'ノートパソコン', 89800, 15),
(2, 'ワイヤレスマウス', 2980, 50),
(3, 'キーボード', 5800, 30),
(4, 'モニター', 24800, 20),
(5, 'Webカメラ', 7800, 25);

-- 全データの取得
SELECT * FROM products;

-- 特定の列のみ取得
SELECT name, price FROM products;

-- 新しいデータの挿入
INSERT INTO products (product_id, name, price, stock)
VALUES (6, 'ヘッドセット', 4500, 40);

-- 挿入後の確認
SELECT * FROM products;

ヒント:INSERT文では、列名の並び(name, price, stock)と値の並びを対応させる。

考察ポイント:INSERT文の実行前と実行後の SELECT * FROM products の結果を比較し、行が1件増えていることを確認する。

演習3.データベース設計と正規化

この演習で学ぶこと:正規化の概念、正規化前後のテーブル構造の違い、データの重複を排除する設計手法
SQLiteのデータ型について:SQLiteは型の扱いが柔軟で、本教材では文字列をTEXT、数値をINTEGERで表す。SQLiteには専用の日付型がないため、日付は'2023-01-15'のような文字列(TEXT)として保存する。この形式(年-月-日)なら文字列のまま並べ替えや比較ができる。

手順:Webブラウザで OneCompiler の SQLite ページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。エディタに以下のSQLをすべて貼り付け、「Run」をクリックする。出力(Output)セクションに表示される結果を確認する。なお、新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

-- ▼ 正規化前のテーブル(問題のある設計)
-- 同じ顧客情報(山田太郎)が複数回登場している
CREATE TABLE orders_before (
    order_id INTEGER PRIMARY KEY,
    customer_name TEXT,
    customer_email TEXT,
    product_name TEXT,
    product_price INTEGER,
    order_date TEXT
);

INSERT INTO orders_before VALUES
(1, '山田太郎', 'yamada@example.com', 'ノートパソコン', 89800, '2023-01-15'),
(2, '山田太郎', 'yamada@example.com', 'マウス', 2980, '2023-01-15'),
(3, '佐藤花子', 'sato@example.com', 'モニター', 24800, '2023-01-20');

-- ▼ 正規化後のテーブル(適切な設計)
-- 顧客・商品・注文を別テーブルに分離
CREATE TABLE customers (
    customer_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
);

CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price INTEGER NOT NULL
);

CREATE TABLE orders_after (
    order_id INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    order_date TEXT NOT NULL
);

INSERT INTO customers VALUES (1, '山田太郎', 'yamada@example.com');
INSERT INTO customers VALUES (2, '佐藤花子', 'sato@example.com');

INSERT INTO products VALUES (1, 'ノートパソコン', 89800);
INSERT INTO products VALUES (2, 'マウス', 2980);
INSERT INTO products VALUES (3, 'モニター', 24800);

INSERT INTO orders_after VALUES (1, 1, 1, '2023-01-15');
INSERT INTO orders_after VALUES (2, 1, 2, '2023-01-15');
INSERT INTO orders_after VALUES (3, 2, 3, '2023-01-20');

-- 正規化前:顧客情報が重複している
SELECT * FROM orders_before;

-- 正規化後:顧客情報は1回のみ保存
SELECT * FROM customers;

-- 正規化後:テーブルを結合してデータを取得
SELECT c.name, p.name AS product, o.order_date
FROM orders_after o
JOIN customers c ON o.customer_id = c.customer_id
JOIN products p ON o.product_id = p.product_id;

ヒント:最後のSELECT文で使われているJOINは、複数のテーブルを共通の列(ここではcustomer_idproduct_id)で結びつけ、1つの結果として取り出すSQLの機能である。ASは列や取得結果に別の名前(別名)を付ける機能である。ここでは、正規化によって分離したcustomersproductsorders_afterの3つのテーブルを結合し、顧客名・商品名・注文日を1つの表として表示している。JOINの詳しい書き方は演習6で扱う。

考察ポイント:orders_before では山田太郎の氏名とメールが2行に重複している。正規化後の customers では1回だけ保存されていることを確認し、重複が排除された点を読み取る。

正規化の注意点:正規化を進めるとJOINが増えて検索性能に影響する場合がある。要件に応じて正規化の程度を判断する。

演習4.データの検索(WHERE句)

この演習で学ぶこと:WHERE句による条件指定、比較演算子の使い方

手順:Webブラウザで OneCompiler の SQLite ページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。エディタに以下のSQLをすべて貼り付け、「Run」をクリックする。出力(Output)セクションに表示される結果を確認する。なお、新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price INTEGER NOT NULL,
    stock INTEGER NOT NULL DEFAULT 0
);

INSERT INTO products (product_id, name, price, stock) VALUES
(1, 'ノートパソコン', 89800, 15),
(2, 'ワイヤレスマウス', 2980, 50),
(3, 'キーボード', 5800, 30),
(4, 'モニター', 24800, 20),
(5, 'Webカメラ', 7800, 25),
(6, 'ヘッドセット', 4500, 40),
(7, 'USBメモリ', 1280, 100),
(8, '外付けHDD', 8900, 35);

-- 価格が5000円より高い商品
SELECT * FROM products WHERE price > 5000;

-- 在庫が30以上の商品
SELECT * FROM products WHERE stock >= 30;

-- 価格が1000円以上かつ5000円以下(AND)
SELECT * FROM products WHERE price >= 1000 AND price <= 5000;

-- 価格が3000円未満または80000円以上(OR)
SELECT * FROM products WHERE price < 3000 OR price >= 80000;

ヒント:AND は両方の条件を満たす行、OR はどちらか一方を満たす行を取得する。

考察ポイント:各クエリで取得される行数を比較し、条件を厳しくする(ANDを使う)と行数が減り、条件を緩める(ORを使う)と行数が増える傾向を読み取る。

演習5.SELECT文の応用(BETWEEN・LIKE・ORDER BY)

この演習で学ぶこと:BETWEEN、LIKE、ORDER BYの使い方

手順:Webブラウザで OneCompiler の SQLite ページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。エディタに以下のSQLをすべて貼り付け、「Run」をクリックする。出力(Output)セクションに表示される結果を確認する。なお、新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price INTEGER NOT NULL,
    stock INTEGER NOT NULL DEFAULT 0
);

INSERT INTO products (product_id, name, price, stock) VALUES
(1, 'ノートパソコン', 89800, 15),
(2, 'ワイヤレスマウス', 2980, 50),
(3, 'ワイヤレスキーボード', 5800, 30),
(4, 'モニター', 24800, 20),
(5, 'Webカメラ', 7800, 25),
(6, 'ヘッドセット', 4500, 40),
(7, 'USBメモリ', 1280, 100),
(8, '外付けHDD', 8900, 35);

-- BETWEEN:価格が1000円から5000円の範囲
SELECT * FROM products WHERE price BETWEEN 1000 AND 5000;

-- LIKE:「ワイヤレス」を含む商品名(%は任意の文字列)
SELECT * FROM products WHERE name LIKE '%ワイヤレス%';

-- ORDER BY:価格の高い順
SELECT * FROM products ORDER BY price DESC;

-- 複数条件での並び替え:在庫数の多い順、同じ場合は価格の安い順
SELECT * FROM products ORDER BY stock DESC, price ASC;

ヒント:BETWEEN の範囲には両端の値(1000と5000)を含む。ORDER BY に2つの列を指定すると、1列目が同じ値の行だけ2列目で並べ替える。

考察ポイント:LIKE の結果に「ワイヤレスマウス」と「ワイヤレスキーボード」が含まれることを確認する。複数条件の ORDER BY で、在庫数が同じ行が価格順に並んでいるかを読み取る。

演習6.テーブルの結合(JOIN)

この演習で学ぶこと:JOIN句による複数テーブルの結合

手順:Webブラウザで OneCompiler の SQLite ページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。エディタに以下のSQLをすべて貼り付け、「Run」をクリックする。出力(Output)セクションに表示される結果を確認する。なお、新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

CREATE TABLE customers (
    customer_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT NOT NULL
);

CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price INTEGER NOT NULL
);

CREATE TABLE orders (
    order_id INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    quantity INTEGER NOT NULL DEFAULT 1,
    order_date TEXT NOT NULL
);

INSERT INTO customers VALUES (1, '山田太郎', 'yamada@example.com');
INSERT INTO customers VALUES (2, '佐藤花子', 'sato@example.com');
INSERT INTO customers VALUES (3, '鈴木一郎', 'suzuki@example.com');

INSERT INTO products VALUES (1, 'ノートパソコン', 89800);
INSERT INTO products VALUES (2, 'マウス', 2980);
INSERT INTO products VALUES (3, 'モニター', 24800);

INSERT INTO orders VALUES (1, 1, 1, 1, '2023-01-15');
INSERT INTO orders VALUES (2, 1, 2, 2, '2023-01-15');
INSERT INTO orders VALUES (3, 2, 3, 1, '2023-01-20');
INSERT INTO orders VALUES (4, 3, 2, 3, '2023-01-22');

-- 2つのテーブルを結合:顧客名と注文日を表示
SELECT c.name, o.order_date
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id;

-- 3つのテーブルを結合:注文詳細を表示
SELECT
    c.name AS customer_name,
    p.name AS product_name,
    o.quantity,
    p.price,
    (p.price * o.quantity) AS total
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN products p ON o.product_id = p.product_id;

ヒント:ON句で「どの列どうしを対応させるか」を指定する。ここでは customer_idproduct_id が共通の列である。

考察ポイント:注文テーブルには商品名や価格が含まれていないが、JOINによって商品テーブルから商品名・価格を取得できることを確認する。total 列が price × quantity で計算されている点を読み取る。

演習7.データの集計(GROUP BY)

この演習で学ぶこと:GROUP BY句によるグループ化、集計関数、HAVING句

手順:Webブラウザで OneCompiler の SQLite ページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。エディタに以下のSQLをすべて貼り付け、「Run」をクリックする。出力(Output)セクションに表示される結果を確認する。なお、新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

CREATE TABLE customers (
    customer_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price INTEGER NOT NULL
);

CREATE TABLE orders (
    order_id INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    quantity INTEGER NOT NULL,
    order_date TEXT NOT NULL
);

INSERT INTO customers VALUES (1, '山田太郎');
INSERT INTO customers VALUES (2, '佐藤花子');
INSERT INTO customers VALUES (3, '鈴木一郎');

INSERT INTO products VALUES (1, 'ノートパソコン', 89800);
INSERT INTO products VALUES (2, 'マウス', 2980);
INSERT INTO products VALUES (3, 'モニター', 24800);

INSERT INTO orders VALUES (1, 1, 1, 1, '2023-01-15');
INSERT INTO orders VALUES (2, 1, 2, 2, '2023-01-15');
INSERT INTO orders VALUES (3, 2, 3, 1, '2023-02-20');
INSERT INTO orders VALUES (4, 1, 2, 1, '2023-02-25');
INSERT INTO orders VALUES (5, 3, 1, 1, '2023-03-10');
INSERT INTO orders VALUES (6, 2, 2, 3, '2023-03-15');

-- 顧客別の注文回数
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id;

-- 顧客別の合計金額
SELECT
    c.name,
    COUNT(o.order_id) AS order_count,
    SUM(p.price * o.quantity) AS total_amount
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN products p ON o.product_id = p.product_id
GROUP BY c.customer_id;

-- HAVING:2回以上注文した顧客のみ
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= 2;

ヒント:COUNT(*) はグループ内の行数を数える。HAVING はグループ化した後の結果に対する条件を指定する。

考察ポイント:顧客別の注文回数と、HAVINGで絞り込んだ結果を比較し、注文回数が1回の顧客が除外されていることを確認する。

演習8.副問い合わせ(サブクエリ)

この演習で学ぶこと:クエリの中にクエリを含めるサブクエリの使い方

手順:Webブラウザで OneCompiler の SQLite ページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。エディタに以下のSQLをすべて貼り付け、「Run」をクリックする。出力(Output)セクションに表示される結果を確認する。なお、新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price INTEGER NOT NULL,
    stock INTEGER NOT NULL
);

INSERT INTO products VALUES (1, 'ノートパソコン', 89800, 15);
INSERT INTO products VALUES (2, 'マウス', 2980, 50);
INSERT INTO products VALUES (3, 'キーボード', 5800, 30);
INSERT INTO products VALUES (4, 'モニター', 24800, 20);
INSERT INTO products VALUES (5, 'Webカメラ', 7800, 25);

-- 平均価格を確認
SELECT AVG(price) AS avg_price FROM products;

-- 平均価格より高い商品を取得(サブクエリ使用)
SELECT * FROM products
WHERE price > (SELECT AVG(price) FROM products);

-- 最高価格の商品を取得
SELECT * FROM products
WHERE price = (SELECT MAX(price) FROM products);

-- 最低価格の商品を取得
SELECT * FROM products
WHERE price = (SELECT MIN(price) FROM products);

ヒント:カッコ内のサブクエリが先に実行され、その結果(平均値や最大値)を外側のWHERE句が条件として使う。

考察ポイント:1番目のクエリで表示される平均価格と、2番目のクエリで取得される商品の価格を見比べ、平均より高い商品だけが取得されていることを確認する。

演習9.主キーと外部キー

この演習で学ぶこと:主キーと外部キーによるテーブル間の関係定義

手順:Webブラウザで OneCompiler の SQLite ページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。エディタに以下のSQLをすべて貼り付け、「Run」をクリックする。出力(Output)セクションに表示される結果を確認する。なお、新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

-- SQLiteで外部キー制約を有効化する(接続ごとに必要)
PRAGMA foreign_keys = ON;

-- 親テーブル(主キーを持つ)
CREATE TABLE categories (
    category_id INTEGER PRIMARY KEY,
    category_name TEXT NOT NULL UNIQUE
);

-- 子テーブル(外部キーで親テーブルを参照)
CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price INTEGER NOT NULL,
    category_id INTEGER,
    FOREIGN KEY (category_id) REFERENCES categories(category_id)
);

INSERT INTO categories VALUES (1, 'コンピュータ');
INSERT INTO categories VALUES (2, '周辺機器');
INSERT INTO categories VALUES (3, 'ストレージ');

INSERT INTO products VALUES (1, 'ノートパソコン', 89800, 1);
INSERT INTO products VALUES (2, 'マウス', 2980, 2);
INSERT INTO products VALUES (3, 'キーボード', 5800, 2);
INSERT INTO products VALUES (4, 'USBメモリ', 1280, 3);

-- カテゴリと商品を結合して表示
SELECT
    p.name AS product_name,
    p.price,
    c.category_name
FROM products p
JOIN categories c ON p.category_id = c.category_id;

-- カテゴリ別の商品数(LEFT JOINで商品ゼロのカテゴリも表示)
SELECT
    c.category_name,
    COUNT(p.product_id) AS product_count
FROM categories c
LEFT JOIN products p ON c.category_id = p.category_id
GROUP BY c.category_id;

ヒント:LEFT JOIN は左側のテーブル(categories)の全行を残し、対応する右側の行がなければ空欄として結合する。

考察ポイント:カテゴリ別の商品数を確認し、各カテゴリに何件の商品が属しているかを読み取る。

注意事項:SQLiteでは外部キー制約はデフォルトで無効であり、接続ごとにPRAGMA foreign_keys = ON;を実行して有効化する。オンラインのSQL実行環境では、このPRAGMAが効かず外部キー制約が検証されない場合がある。

演習10.HTMLの基礎とフォーム

この演習で学ぶこと:HTMLの基本構造、フォーム要素の使い方

手順:以下のHTMLコードを index.html という名前で保存する。保存したファイルをダブルクリックし、Webブラウザで開く。このHTMLファイルは表示のみを目的としており、サーバ側の処理を持たない。したがって、フォームの「登録」ボタンを押しても、送信先である /add にはプログラムが存在しないため、ブラウザにエラー(ファイルが見つからない旨の表示など)が表示される。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>商品管理</title>
</head>
<body>
    <h1>商品一覧</h1>
    <table border="1">
        <tr>
            <th>商品名</th>
            <th>価格</th>
        </tr>
        <tr>
            <td>ノートパソコン</td>
            <td>89,800円</td>
        </tr>
        <tr>
            <td>マウス</td>
            <td>2,980円</td>
        </tr>
    </table>

    <h2>商品登録フォーム</h2>
    <form action="/add" method="post">
        <p>
            <label for="name">商品名:</label>
            <input type="text" id="name" name="name" required>
        </p>
        <p>
            <label for="price">価格:</label>
            <input type="number" id="price" name="price" required>
        </p>
        <p>
            <input type="submit" value="登録">
        </p>
    </form>
</body>
</html>

ヒント:required属性は入力必須を表す。method="post" はフォームデータをPOSTメソッドで送信する指定である。

考察ポイント:表とフォームがブラウザにどう表示されるかを確認する。商品名や価格を空のまま「登録」を押すと、required属性により送信が止まることを確認する。

演習11.PythonとFlask入門

この演習で学ぶこと:Flaskの基本構造、ルーティングの仕組み

手順:Windows上に作業用フォルダを作成する(例:ドキュメント\flask_lesson11)。以下のコードを main.py という名前でそのフォルダに保存する。コマンドプロンプトを開き、cd コマンドで作業用フォルダに移動する(例:cd %USERPROFILE%\Documents\flask_lesson11)。python main.py を実行する。Webブラウザで http://localhost:5000 にアクセスして結果を確認する。サーバを停止するには、コマンドプロンプトで Ctrl キーと C キーを同時に押す。初回実行時に「Windows セキュリティの重要な警告」が表示された場合は「アクセスを許可する」を選択する。これは、自分のパソコン上で動作する開発用サーバ(localhost)への接続を許可するための確認である。この手順は、Pythonと Flask のインストールが完了していることを前提とする。

from flask import Flask

app = Flask(__name__)

# ルーティング:URLと関数を対応付ける
@app.route('/')
def home():
    return '''
    <h1>Flaskアプリケーション</h1>
    <p>Flaskで作成したWebページです。</p>
    <ul>
        <li><a href="/about">このサイトについて</a></li>
        <li><a href="/products">商品一覧</a></li>
    </ul>
    '''

@app.route('/about')
def about():
    return '''
    <h1>このサイトについて</h1>
    <p>Flaskの学習用サンプルです。</p>
    <p><a href="/">トップに戻る</a></p>
    '''

@app.route('/products')
def products():
    return '''
    <h1>商品一覧</h1>
    <ul>
        <li>ノートパソコン - 89,800円</li>
        <li>マウス - 2,980円</li>
        <li>キーボード - 5,800円</li>
    </ul>
    <p><a href="/">トップに戻る</a></p>
    '''

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5000, debug=True)

ヒント:@app.route('/about')/about がURLの末尾に対応する。http://localhost:5000/about にアクセスすると about 関数が実行される。

考察ポイント:トップページのリンクから /about と /products に移動し、URLの末尾と表示内容の対応を確認する。

補足:host='127.0.0.1' は自分のパソコンのみからアクセスできる設定である。debug=True を指定すると、コードを変更したときに自動で再読み込みされ、エラー内容もブラウザに表示される。これは学習・動作確認に適した開発用サーバの設定である。

演習12.データベースとWebの連携

この演習で学ぶこと:FlaskからSQLiteデータベースへの接続、テンプレートを用いた動的ページ生成

手順:Windows上に作業用フォルダを作成する(例:ドキュメント\flask_lesson12)。以下の main.py をそのフォルダに保存する。main.py と同じ場所に templates という名前のフォルダを作成し、その中に、以下で示す products.html を保存する。コマンドプロンプトを開き、cd コマンドで作業用フォルダに移動する(例:cd %USERPROFILE%\Documents\flask_lesson12)。python main.py を実行する。Webブラウザで http://localhost:5000 にアクセスして結果を確認する。サーバを停止するには、コマンドプロンプトで Ctrl キーと C キーを同時に押す。初回実行時に「Windows セキュリティの重要な警告」が表示された場合は「アクセスを許可する」を選択する。これは、自分のパソコン上で動作する開発用サーバ(localhost)への接続を許可するための確認である。この手順は、Pythonと Flask のインストールが完了していることを前提とする。

main.py:

from flask import Flask, render_template
import sqlite3

app = Flask(__name__)

def init_db():
    """データベースを初期化し、サンプルデータを挿入する"""
    conn = sqlite3.connect('shop.db')
    conn.execute('''
        CREATE TABLE IF NOT EXISTS products (
            product_id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            price INTEGER NOT NULL
        )
    ''')
    # テーブルが空の場合のみサンプルデータを挿入
    count = conn.execute('SELECT COUNT(*) FROM products').fetchone()[0]
    if count == 0:
        conn.execute("INSERT INTO products VALUES (1, 'ノートパソコン', 89800)")
        conn.execute("INSERT INTO products VALUES (2, 'マウス', 2980)")
        conn.execute("INSERT INTO products VALUES (3, 'キーボード', 5800)")
        conn.commit()
    conn.close()

@app.route('/')
def index():
    """商品一覧を表示する"""
    conn = sqlite3.connect('shop.db')
    conn.row_factory = sqlite3.Row  # 列名でアクセス可能にする
    products = conn.execute('SELECT * FROM products').fetchall()
    conn.close()
    return render_template('products.html', products=products)

if __name__ == '__main__':
    init_db()
    app.run(host='127.0.0.1', port=5000, debug=True)

templates/products.html:

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>商品一覧</title>
</head>
<body>
    <h1>商品一覧</h1>
    <table border="1">
        <tr>
            <th>ID</th>
            <th>商品名</th>
            <th>価格</th>
        </tr>
        <!-- {% for %}でループ処理 -->
        {% for product in products %}
        <tr>
            <td>{{ product.product_id }}</td>
            <td>{{ product.name }}</td>
            <td>{{ product.price }}円</td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

ヒント:conn.row_factory = sqlite3.Row を設定すると、テンプレート内で product.name のように列名でデータを参照できる。

考察ポイント:ブラウザに表示される表の各行が、データベースの3件のデータと対応していることを確認する。{% for %} のループによって、データ件数だけ行が生成される点を読み取る。

補足:このプログラムが使うデータベースファイル(shop.db)は、プログラムを実行するとmain.pyと同じフォルダに自動で作成される。サンプルデータを変更する場合や、最初からやり直す場合は、このファイルを削除してからmain.pyを再実行する。削除すると空の状態から作り直され、サンプルデータも再投入される。

演習13.CRUD操作の実装

この演習で学ぶこと:データの追加・読取・更新・削除(CRUD)機能、フォーム処理、SQLインジェクション対策

手順:Windows上に作業用フォルダを作成する(例:ドキュメント\flask_lesson13)。以下の main.py をそのフォルダに保存する。main.py と同じ場所に templates という名前のフォルダを作成し、その中に、以下で示す index.htmledit.html を保存する。コマンドプロンプトを開き、cd コマンドで作業用フォルダに移動する(例:cd %USERPROFILE%\Documents\flask_lesson13)。python main.py を実行する。Webブラウザで http://localhost:5000 にアクセスして結果を確認する。サーバを停止するには、コマンドプロンプトで Ctrl キーと C キーを同時に押す。初回実行時に「Windows セキュリティの重要な警告」が表示された場合は「アクセスを許可する」を選択する。これは、自分のパソコン上で動作する開発用サーバ(localhost)への接続を許可するための確認である。この手順は、Pythonと Flask のインストールが完了していることを前提とする。

main.py:

from flask import Flask, render_template, request, redirect, url_for
import sqlite3

app = Flask(__name__)

def get_db():
    """データベース接続を取得する"""
    conn = sqlite3.connect('shop.db')
    conn.row_factory = sqlite3.Row
    return conn

def init_db():
    """データベースを初期化する"""
    conn = get_db()
    conn.execute('''
        CREATE TABLE IF NOT EXISTS products (
            product_id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            price INTEGER NOT NULL
        )
    ''')
    conn.commit()
    conn.close()

@app.route('/')
def index():
    """商品一覧を表示する(Read)"""
    conn = get_db()
    products = conn.execute('SELECT * FROM products').fetchall()
    conn.close()
    return render_template('index.html', products=products)

@app.route('/add', methods=['POST'])
def add_product():
    """商品を追加する(Create)"""
    name = request.form['name']
    price = request.form['price']
    conn = get_db()
    # プレースホルダ(?)でSQLインジェクションを防止
    conn.execute('INSERT INTO products (name, price) VALUES (?, ?)', (name, price))
    conn.commit()
    conn.close()
    return redirect(url_for('index'))

@app.route('/edit/')
def edit_form(product_id):
    """更新フォームを表示する(Updateの入力画面)"""
    conn = get_db()
    product = conn.execute(
        'SELECT * FROM products WHERE product_id = ?', (product_id,)
    ).fetchone()
    conn.close()
    return render_template('edit.html', product=product)

@app.route('/update/', methods=['POST'])
def update_product(product_id):
    """商品を更新する(Update)"""
    name = request.form['name']
    price = request.form['price']
    conn = get_db()
    conn.execute(
        'UPDATE products SET name = ?, price = ? WHERE product_id = ?',
        (name, price, product_id)
    )
    conn.commit()
    conn.close()
    return redirect(url_for('index'))

@app.route('/delete/', methods=['POST'])
def delete_product(product_id):
    """商品を削除する(Delete)"""
    conn = get_db()
    conn.execute('DELETE FROM products WHERE product_id = ?', (product_id,))
    conn.commit()
    conn.close()
    return redirect(url_for('index'))

if __name__ == '__main__':
    init_db()
    app.run(host='127.0.0.1', port=5000, debug=True)

templates/index.html:

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>商品管理</title>
</head>
<body>
    <h1>商品管理</h1>

    <h2>商品追加</h2>
    <form action="/add" method="post">
        <p>商品名: <input type="text" name="name" required></p>
        <p>価格: <input type="number" name="price" required></p>
        <p><input type="submit" value="追加"></p>
    </form>

    <h2>商品一覧</h2>
    <table border="1">
        <tr>
            <th>ID</th>
            <th>商品名</th>
            <th>価格</th>
            <th>操作</th>
        </tr>
        {% for product in products %}
        <tr>
            <td>{{ product.product_id }}</td>
            <td>{{ product.name }}</td>
            <td>{{ product.price }}円</td>
            <td>
                <a href="/edit/{{ product.product_id }}">編集</a>
                <form action="/delete/{{ product.product_id }}" method="post" style="display:inline">
                    <input type="submit" value="削除">
                </form>
            </td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

templates/edit.html:

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>商品の編集</title>
</head>
<body>
    <h1>商品の編集(ID: {{ product.product_id }})</h1>

    <!-- 更新内容を /update に送信する -->
    <form action="/update/{{ product.product_id }}" method="post">
        <p>商品名: <input type="text" name="name" value="{{ product.name }}" required></p>
        <p>価格: <input type="number" name="price" value="{{ product.price }}" required></p>
        <p><input type="submit" value="更新"></p>
    </form>

    <p><a href="/">一覧に戻る</a></p>
</body>
</html>

ヒント:編集リンクを押すと /edit/<ID> で現在の値が入った更新フォームが開き、更新ボタンで /update/<ID> に送信される。

考察ポイント:商品を追加・編集・削除した後、一覧表示がどう変化するかを確認する。それぞれの操作がCRUDのどの機能(INSERT・SELECT・UPDATE・DELETE)に対応するかを読み取る。

CRUDと操作の対応:このアプリでは、追加(Create)がINSERT、一覧表示(Read)がSELECT、更新(Update)がUPDATE、削除(Delete)がDELETEに対応する。Pythonを介さず直接書くと、ID1の商品を削除するSQLはDELETE FROM products WHERE product_id = 1;、価格を更新するSQLはUPDATE products SET price = 5000 WHERE product_id = 1;となる。
セキュリティ上の注意事項:
  • SQLインジェクション対策:プレースホルダ(?)を使用し、ユーザ入力を直接SQL文に埋め込まない
  • 更新・削除のようにデータを変更する操作は、リンク(GET)ではなくPOSTで送信する。上の例では削除と更新の確定をフォーム(method="post")にしている。「編集」リンクは入力フォームを表示するだけ(データを変更しない)なので、リンク(GET)でよい
補足:このプログラムが使うデータベースファイル(shop.db)は、プログラムを実行するとmain.pyと同じフォルダに自動で作成される。最初からやり直す場合は、このファイルを削除してからmain.pyを再実行する。削除すると、商品が1件も登録されていない空のテーブルから作り直される。

演習14.データ集計とレポート機能

この演習で学ぶこと:SQLによるデータ集計、レポート表示、JSON形式でのデータ出力

手順:Windows上に作業用フォルダを作成する(例:ドキュメント\flask_lesson14)。以下の main.py をそのフォルダに保存する。main.py と同じ場所に templates という名前のフォルダを作成し、その中に、以下で示す report.html を保存する。コマンドプロンプトを開き、cd コマンドで作業用フォルダに移動する(例:cd %USERPROFILE%\Documents\flask_lesson14)。python main.py を実行する。Webブラウザで http://localhost:5000 にアクセスして結果を確認する。サーバを停止するには、コマンドプロンプトで Ctrl キーと C キーを同時に押す。初回実行時に「Windows セキュリティの重要な警告」が表示された場合は「アクセスを許可する」を選択する。これは、自分のパソコン上で動作する開発用サーバ(localhost)への接続を許可するための確認である。この手順は、Pythonと Flask のインストールが完了していることを前提とする。

main.py:

from flask import Flask, render_template, jsonify
import sqlite3

app = Flask(__name__)

def init_db():
    """データベースを初期化し、サンプルデータを挿入する"""
    conn = sqlite3.connect('sales.db')
    conn.execute('''
        CREATE TABLE IF NOT EXISTS orders (
            order_id INTEGER PRIMARY KEY,
            product_name TEXT NOT NULL,
            quantity INTEGER NOT NULL,
            price INTEGER NOT NULL,
            order_month TEXT NOT NULL
        )
    ''')
    # テーブルが空の場合のみサンプルデータを挿入
    count = conn.execute('SELECT COUNT(*) FROM orders').fetchone()[0]
    if count == 0:
        orders = [
            (1, 'ノートパソコン', 2, 89800, '2023-01'),
            (2, 'マウス', 10, 2980, '2023-01'),
            (3, 'キーボード', 5, 5800, '2023-01'),
            (4, 'ノートパソコン', 3, 89800, '2023-02'),
            (5, 'マウス', 8, 2980, '2023-02'),
            (6, 'モニター', 4, 24800, '2023-02'),
        ]
        conn.executemany('INSERT INTO orders VALUES (?, ?, ?, ?, ?)', orders)
        conn.commit()
    conn.close()

@app.route('/')
def report():
    """売上レポートを表示する"""
    conn = sqlite3.connect('sales.db')
    conn.row_factory = sqlite3.Row

    # 月別売上集計
    monthly = conn.execute('''
        SELECT order_month, SUM(quantity * price) AS total
        FROM orders
        GROUP BY order_month
        ORDER BY order_month
    ''').fetchall()

    # 商品別売上集計
    by_product = conn.execute('''
        SELECT product_name, SUM(quantity) AS qty, SUM(quantity * price) AS total
        FROM orders
        GROUP BY product_name
        ORDER BY total DESC
    ''').fetchall()

    conn.close()
    return render_template('report.html', monthly=monthly, by_product=by_product)

@app.route('/api/sales')
def api_sales():
    """売上データをJSON形式で返す"""
    conn = sqlite3.connect('sales.db')
    conn.row_factory = sqlite3.Row
    rows = conn.execute('''
        SELECT product_name, SUM(quantity * price) AS total
        FROM orders
        GROUP BY product_name
    ''').fetchall()
    conn.close()
    # dict(row)でsqlite3.RowをPythonの辞書に変換
    return jsonify([dict(row) for row in rows])

if __name__ == '__main__':
    init_db()
    app.run(host='127.0.0.1', port=5000, debug=True)

templates/report.html:

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>売上レポート</title>
</head>
<body>
    <h1>売上レポート</h1>

    <h2>月別売上</h2>
    <table border="1">
        <tr><th>月</th><th>売上合計</th></tr>
        {% for row in monthly %}
        <tr>
            <td>{{ row.order_month }}</td>
            <td>{{ row.total }}円</td>
        </tr>
        {% endfor %}
    </table>

    <h2>商品別売上</h2>
    <table border="1">
        <tr><th>商品名</th><th>販売数</th><th>売上合計</th></tr>
        {% for row in by_product %}
        <tr>
            <td>{{ row.product_name }}</td>
            <td>{{ row.qty }}</td>
            <td>{{ row.total }}円</td>
        </tr>
        {% endfor %}
    </table>

    <h2>API</h2>
    <p><a href="/api/sales">JSON形式で取得</a></p>
</body>
</html>

ヒント:月別売上は order_month でグループ化し、商品別売上は product_name でグループ化している。SUM(quantity * price) がグループごとの売上合計になる。

考察ポイント:画面の表と、/api/sales が返すJSONを見比べ、同じ集計結果が表とJSONの2つの形式で出力されていることを確認する。

補足:このプログラムが使うデータベースファイル(sales.db)は、プログラムを実行するとmain.pyと同じフォルダに自動で作成される。サンプルデータを変更する場合や、最初からやり直す場合は、このファイルを削除してからmain.pyを再実行する。削除すると空の状態から作り直され、サンプルデータも再投入される。

演習15.パフォーマンス最適化とまとめ

この演習で学ぶこと:インデックスによる検索の高速化、ビューによるクエリの簡素化、学習の総まとめ

手順:Webブラウザで OneCompiler の SQLite ページ(https://onecompiler.com/sqlite)にアクセスする(アカウント登録不要)。エディタに以下のSQLをすべて貼り付け、「Run」をクリックする。出力(Output)セクションに表示される結果を確認する。なお、新しいクエリを試す場合は、これまでのテーブル作成文とデータ挿入文を含めて、コード全体を毎回まとめて記述し、実行する。

CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price INTEGER NOT NULL,
    category TEXT NOT NULL
);

CREATE TABLE orders (
    order_id INTEGER PRIMARY KEY,
    product_id INTEGER NOT NULL,
    quantity INTEGER NOT NULL,
    order_date TEXT NOT NULL
);

INSERT INTO products VALUES (1, 'ノートパソコン', 89800, 'PC');
INSERT INTO products VALUES (2, 'マウス', 2980, '周辺機器');
INSERT INTO products VALUES (3, 'キーボード', 5800, '周辺機器');
INSERT INTO products VALUES (4, 'モニター', 24800, '周辺機器');
INSERT INTO products VALUES (5, 'USBメモリ', 1280, 'ストレージ');

INSERT INTO orders VALUES (1, 1, 2, '2023-01-15');
INSERT INTO orders VALUES (2, 2, 5, '2023-01-20');
INSERT INTO orders VALUES (3, 1, 1, '2023-02-10');
INSERT INTO orders VALUES (4, 3, 3, '2023-02-15');
INSERT INTO orders VALUES (5, 4, 2, '2023-03-01');

-- インデックスの作成(検索性能の向上)
CREATE INDEX idx_products_price ON products(price);
CREATE INDEX idx_orders_date ON orders(order_date);

-- ビューの作成(クエリの簡素化)
CREATE VIEW order_summary AS
SELECT
    p.name AS product_name,
    p.category,
    SUM(o.quantity) AS total_qty,
    SUM(o.quantity * p.price) AS total_amount
FROM orders o
JOIN products p ON o.product_id = p.product_id
GROUP BY p.product_id;

-- ビューを使用:JOINと集計を簡潔に記述
SELECT * FROM order_summary;

-- インデックスが使用される検索
SELECT * FROM products WHERE price > 5000;

-- カテゴリ別の売上(ビューを使用)
SELECT category, SUM(total_amount) AS category_total
FROM order_summary
GROUP BY category;

-- 実行計画の確認
EXPLAIN QUERY PLAN
SELECT * FROM products WHERE price > 5000;

ヒント:ビューは保存したクエリで、通常のテーブルと同じように SELECT できる。EXPLAIN QUERY PLAN はクエリの処理方法を表示する。

考察ポイント:ビュー order_summary を使ったクエリが、JOINと集計を毎回書かずに済むことを確認する。EXPLAIN QUERY PLAN の出力で、作成したインデックス(idx_products_price)が検索に使われているかを読み取る。

インデックス作成時の注意事項:
  • 検索条件として頻繁に使用する列に作成する
  • インデックスが多いとINSERT、UPDATE、DELETEの処理が遅くなる
  • 小規模なテーブルではインデックスの効果が小さい
学習のまとめ:
  • リレーショナルデータベースの基礎(テーブル設計、正規化、キー制約)
  • SQLによるデータ取得・集計(SELECT、WHERE、JOIN、GROUP BY、サブクエリ)
  • データの追加・更新・削除(FlaskとSQLiteによるCRUDの実装。SQLとしてはINSERT、UPDATE、DELETE)
  • Webアプリケーション開発(HTML、Flask、テンプレート)
  • パフォーマンス最適化(インデックス、ビュー)