348 lines
10 KiB
PHP
348 lines
10 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../database/products/ProductAccess.php';
|
|
|
|
/**
|
|
* JOIN を使った高度な検索(追加課題)
|
|
*
|
|
* このファイルでは、categoriesテーブルとproductsテーブルを結合して、
|
|
* カテゴリ情報と共に商品情報を取得します。
|
|
* また、カテゴリ別の商品数を集計する機能も実装します。
|
|
*/
|
|
|
|
// エラー表示設定(開発時のみ)
|
|
ini_set('display_errors', 1);
|
|
error_reporting(E_ALL);
|
|
|
|
$message = '';
|
|
$messageType = '';
|
|
$productsWithCategory = [];
|
|
$categoryStats = [];
|
|
|
|
try {
|
|
// ProductAccessを使ってPDO接続を取得
|
|
$productAccess = new ProductAccess();
|
|
$pdo = $productAccess->getConnection();
|
|
|
|
// TODO: INNER JOINを使って、商品情報とカテゴリ情報を結合して取得してください
|
|
$sql = ""; // ここにJOINを使ったSQL文を記述
|
|
|
|
// TODO: ここでSQLを実行して結果を$productsWithCategoryに格納
|
|
|
|
|
|
// TODO: カテゴリ別の商品数と平均価格を集計してください
|
|
$statsSql = ""; // ここに集計用のSQL文を記述
|
|
|
|
// TODO: ここでSQLを実行して結果を$categoryStatsに格納
|
|
} catch (Exception $e) {
|
|
$message = "データの取得に失敗しました: " . $e->getMessage();
|
|
$messageType = "error";
|
|
}
|
|
?>
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="ja">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>JOIN検索 - 追加課題</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
background: white;
|
|
padding: 30px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
h1 {
|
|
color: #333;
|
|
margin-bottom: 10px;
|
|
font-size: 28px;
|
|
}
|
|
|
|
h2 {
|
|
color: #555;
|
|
margin-top: 30px;
|
|
margin-bottom: 15px;
|
|
font-size: 22px;
|
|
border-bottom: 2px solid #11998e;
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
.subtitle {
|
|
color: #666;
|
|
margin-bottom: 25px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.nav-links {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 25px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.nav-links a {
|
|
padding: 8px 16px;
|
|
background: #11998e;
|
|
color: white;
|
|
text-decoration: none;
|
|
border-radius: 5px;
|
|
font-size: 14px;
|
|
transition: background 0.3s;
|
|
}
|
|
|
|
.nav-links a:hover {
|
|
background: #0e7d72;
|
|
}
|
|
|
|
.message {
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
border-radius: 5px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.message.error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
th,
|
|
td {
|
|
padding: 12px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
th {
|
|
background: #11998e;
|
|
color: white;
|
|
font-weight: bold;
|
|
}
|
|
|
|
tr:hover {
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.price {
|
|
text-align: right;
|
|
font-weight: bold;
|
|
color: #e74c3c;
|
|
}
|
|
|
|
.stock {
|
|
text-align: center;
|
|
}
|
|
|
|
.category-badge {
|
|
display: inline-block;
|
|
padding: 4px 12px;
|
|
background: #11998e;
|
|
color: white;
|
|
border-radius: 15px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 20px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.stat-card h3 {
|
|
margin-bottom: 15px;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.stat-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 10px;
|
|
padding-bottom: 10px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.stat-row:last-child {
|
|
border-bottom: none;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 14px;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 16px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.no-data {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: #999;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.info-box {
|
|
background: #e7f3ff;
|
|
border-left: 4px solid #2196F3;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
.info-box h3 {
|
|
color: #1976D2;
|
|
margin-bottom: 10px;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.info-box p {
|
|
color: #555;
|
|
line-height: 1.6;
|
|
font-size: 14px;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="container">
|
|
<h1>🔗 JOIN を使った高度な検索</h1>
|
|
<p class="subtitle">Advanced - 複数テーブルを結合してデータを取得します</p>
|
|
|
|
<div class="nav-links">
|
|
<a href="index.php">🏠 トップ</a>
|
|
<a href="product_list.php">📋 一覧表示</a>
|
|
<a href="product_register.php">📝 新規登録</a>
|
|
<a href="product_update.php">✏️ 更新</a>
|
|
<a href="product_delete.php">🗑️ 削除</a>
|
|
</div>
|
|
|
|
<div class="info-box">
|
|
<h3>📚 追加課題のポイント</h3>
|
|
<p>
|
|
この課題では、<strong>INNER JOIN</strong> と <strong>LEFT JOIN</strong> を使用します。<br>
|
|
・INNER JOIN: カテゴリと商品の両方に存在するデータのみを取得<br>
|
|
・LEFT JOIN: 全カテゴリを取得し、商品がない場合も表示<br>
|
|
・集計関数: COUNT()で商品数、AVG()で平均価格を計算<br>
|
|
・GROUP BY: カテゴリごとにグループ化
|
|
</p>
|
|
</div>
|
|
|
|
<?php if ($message): ?>
|
|
<div class="message <?php echo $messageType; ?>">
|
|
<?php echo htmlspecialchars($message); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- カテゴリ別統計情報 -->
|
|
<h2>📊 カテゴリ別統計情報</h2>
|
|
<?php if (empty($categoryStats)): ?>
|
|
<div class="no-data">
|
|
統計情報を表示できません。<br>
|
|
TODOを実装してください。
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="stats-grid">
|
|
<?php foreach ($categoryStats as $stat): ?>
|
|
<div class="stat-card">
|
|
<h3><?php echo htmlspecialchars($stat['name']); ?></h3>
|
|
<div class="stat-row">
|
|
<span class="stat-label">商品数:</span>
|
|
<span class="stat-value"><?php echo $stat['product_count']; ?> 件</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span class="stat-label">平均価格:</span>
|
|
<span class="stat-value">
|
|
<?php
|
|
echo $stat['avg_price']
|
|
? '¥' . number_format($stat['avg_price'], 0)
|
|
: '商品なし';
|
|
?>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- 商品一覧(カテゴリ情報付き) -->
|
|
<h2>📦 商品一覧(カテゴリ情報付き)</h2>
|
|
<?php if (empty($productsWithCategory)): ?>
|
|
<div class="no-data">
|
|
商品情報を表示できません。<br>
|
|
TODOを実装してください。
|
|
</div>
|
|
<?php else: ?>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>カテゴリ</th>
|
|
<th>商品名</th>
|
|
<th>カテゴリ説明</th>
|
|
<th>価格</th>
|
|
<th>在庫</th>
|
|
<th>登録日時</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($productsWithCategory as $product): ?>
|
|
<tr>
|
|
<td><?php echo htmlspecialchars($product['id']); ?></td>
|
|
<td>
|
|
<span class="category-badge">
|
|
<?php echo htmlspecialchars($product['category_name']); ?>
|
|
</span>
|
|
</td>
|
|
<td><?php echo htmlspecialchars($product['name']); ?></td>
|
|
<td><?php echo htmlspecialchars($product['category_description'] ?? '-'); ?></td>
|
|
<td class="price">¥<?php echo number_format($product['price']); ?></td>
|
|
<td class="stock"><?php echo htmlspecialchars($product['stock']); ?></td>
|
|
<td><?php echo htmlspecialchars($product['created_at']); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<p style="margin-top: 15px; color: #666; font-size: 14px;">
|
|
全 <?php echo count($productsWithCategory); ?> 件の商品が表示されています。
|
|
</p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</body>
|
|
|
|
</html>
|