Relations

Relations

2019/12/29

當資料表之間建立關聯之後,新增或修改資料就必須要注意資料表之間的關係。

假如purchase資料表中,包含user_id、sticker_id、created_at,user_id是外鍵(foreign key)連接到users的主鍵id及stickers的主鍵id。也就是說,在新增一筆purchase時必須確定user_id是存在於users,sticker_id也必須存在於stickers。

ALTER TABLE `purchases`

  ADD CONSTRAINT `purchase_sticker` FOREIGN KEY (`sticker_id`) REFERENCES `stickers` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,

  ADD CONSTRAINT `purchase_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT;

完整資料表的sql如下:

-- phpMyAdmin SQL Dump

-- version 4.9.0.1

-- https://www.phpmyadmin.net/

--

-- 主機: localhost

-- 產生時間: 

-- 伺服器版本: 8.0.17

-- PHP 版本: 7.3.8


SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";

SET AUTOCOMMIT = 0;

START TRANSACTION;

SET time_zone = "+00:00";



/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;

/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;

/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;

/*!40101 SET NAMES utf8mb4 */;


--

-- 資料庫: `web_mid_exam`

--


-- --------------------------------------------------------


--

-- 資料表結構 `purchases`

--


CREATE TABLE `purchases` (

  `id` int(11) NOT NULL,

  `user_id` int(11) NOT NULL,

  `sticker_id` int(11) NOT NULL,

  `created_at` datetime NOT NULL

) ENGINE=InnoDB DEFAULT CHARSET=utf8;


--

-- 傾印資料表的資料 `purchases`

--


INSERT INTO `purchases` (`id`, `user_id`, `sticker_id`, `created_at`) VALUES

(1, 1, 1, '2019-12-29 10:28:46'),

(2, 1, 2, '2019-12-29 10:33:32'),

(3, 1, 3, '2019-12-29 11:43:19'),

(4, 74, 1, '2019-12-29 17:52:45'),

(5, 74, 2, '2019-12-29 17:52:49'),

(6, 74, 3, '2019-12-29 17:52:52');


-- --------------------------------------------------------


--

-- 資料表結構 `stickers`

--


CREATE TABLE `stickers` (

  `id` int(11) NOT NULL,

  `title` varchar(30) NOT NULL,

  `author` varchar(10) NOT NULL,

  `description` text NOT NULL,

  `price` tinyint(4) NOT NULL,

  `created_at` date NOT NULL

) ENGINE=InnoDB DEFAULT CHARSET=utf8;


--

-- 傾印資料表的資料 `stickers`

--


INSERT INTO `stickers` (`id`, `title`, `author`, `description`, `price`, `created_at`) VALUES

(1, '自由之翼貼圖特輯 第一彈', 'TT', '最可愛及搞笑的資管系童鞋們,讓我們看看他們在宿營有什麼有趣的模樣吧!', 60, '2019-10-28'),

(2, '自由之翼貼圖特輯 第二彈', 'TINY', '大家最愛的自由之翼小夥伴又來啦!這群認真又不失搞笑的士官長們這次又會帶來什麼可愛的表現呢?', 60, '2019-11-11'),

(3, '資管小棒槌 貼圖特輯', '踢妮', '可愛的士官長第三彈!不用多說自己看ㄅ❤︎', 60, '2019-11-11'),

(4, '古人撩妹語錄', 'Dcard', '課本沒教的古人名言,撩妹篇。<br>\r\n古人除了寫詩、搞發明還會幹嘛?沒事當然還要來撩撩妹啦。快看哪一句GET到你的心❤︎\r\n', 30, '2019-11-25'),

(5, '汗語字典', 'IG', '課本沒教的單字。<br>\r\n蛤?原來是這個意思啊。看貼圖,長知識。', 30, '2019-11-25');


-- --------------------------------------------------------


--

-- 資料表結構 `users`

--


CREATE TABLE `users` (

  `id` int(11) NOT NULL,

  `role` char(1) NOT NULL,

  `account` varchar(30) NOT NULL,

  `name` varchar(20) DEFAULT NULL,

  `password` varchar(30) NOT NULL,

  `credit` int(11) NOT NULL DEFAULT '0',

  `created_at` date NOT NULL,

  `updated_at` datetime DEFAULT NULL,

  `deleted_at` datetime DEFAULT NULL

) ENGINE=InnoDB DEFAULT CHARSET=utf8;


--

-- 傾印資料表的資料 `users`

--


INSERT INTO `users` (`id`, `role`, `account`, `name`, `password`, `credit`, `created_at`, `updated_at`, `deleted_at`) VALUES

(1, 'M', '405401372', '伍庭儀', '123456', 500, '2019-10-27', '2019-12-29 16:30:13', NULL),

(2, 'M', '405401607', '高子軒', '1234567', 0, '2019-10-27', '2019-12-29 16:30:30', NULL),

(74, 'C', '756951', 'Ben Wu', '123456', 0, '2019-12-29', NULL, NULL);


-- --------------------------------------------------------


--

-- 資料表結構 `wishes`

--


CREATE TABLE `wishes` (

  `id` int(11) NOT NULL,

  `user_id` int(11) NOT NULL,

  `sticker_id` int(11) NOT NULL,

  `created_at` datetime NOT NULL

) ENGINE=InnoDB DEFAULT CHARSET=utf8;


--

-- 傾印資料表的資料 `wishes`

--


INSERT INTO `wishes` (`id`, `user_id`, `sticker_id`, `created_at`) VALUES

(2, 1, 3, '2019-12-29 11:43:39'),

(9, 74, 1, '2019-12-29 17:53:05');


--

-- 已傾印資料表的索引

--


--

-- 資料表索引 `purchases`

--

ALTER TABLE `purchases`

  ADD PRIMARY KEY (`id`),

  ADD KEY `purchase_sticker` (`sticker_id`),

  ADD KEY `purchase_user` (`user_id`);


--

-- 資料表索引 `stickers`

--

ALTER TABLE `stickers`

  ADD PRIMARY KEY (`id`);


--

-- 資料表索引 `users`

--

ALTER TABLE `users`

  ADD PRIMARY KEY (`id`);


--

-- 資料表索引 `wishes`

--

ALTER TABLE `wishes`

  ADD PRIMARY KEY (`id`),

  ADD KEY `wish_user` (`user_id`),

  ADD KEY `wish_sticker` (`sticker_id`);


--

-- 在傾印的資料表使用自動遞增(AUTO_INCREMENT)

--


--

-- 使用資料表自動遞增(AUTO_INCREMENT) `purchases`

--

ALTER TABLE `purchases`

  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;


--

-- 使用資料表自動遞增(AUTO_INCREMENT) `stickers`

--

ALTER TABLE `stickers`

  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;


--

-- 使用資料表自動遞增(AUTO_INCREMENT) `users`

--

ALTER TABLE `users`

  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=75;


--

-- 使用資料表自動遞增(AUTO_INCREMENT) `wishes`

--

ALTER TABLE `wishes`

  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10;


--

-- 已傾印資料表的限制式

--


--

-- 資料表的限制式 `purchases`

--

ALTER TABLE `purchases`

  ADD CONSTRAINT `purchase_sticker` FOREIGN KEY (`sticker_id`) REFERENCES `stickers` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,

  ADD CONSTRAINT `purchase_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT;


--

-- 資料表的限制式 `wishes`

--

ALTER TABLE `wishes`

  ADD CONSTRAINT `wish_sticker` FOREIGN KEY (`sticker_id`) REFERENCES `stickers` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,

  ADD CONSTRAINT `wish_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT;

COMMIT;


/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;

/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;

/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

那如何確保這件事呢? 我們可以利用登錄時,將user_id記錄下來,另外,購買的按鈕也放在貼圖中,利用隱藏欄位自動的代入這兩個欄位,這樣就可以減少資料表資料不一致的問題。

stickerPage.php

<form action="controllers/purchase_process.php" method="post">

  <input type="hidden" name="userId" value="<?= $userId ?>">

  <input type="hidden" name="stickerId" value="<?= $stickerId ?>">

  <input type="submit" class="btn button--purchase" value="購買">

</form>

controllers/purchase_process.php

<?php


require __DIR__ . '/../etc/bootstrap.php';


//確認是否有修改表單資料

if (!empty($_POST)) {


  // =============================================================================

  // = 處理送來的表單資料

  // =============================================================================


  $userId = $_POST["userId"] ?? "";

  $stickerId = $_POST["stickerId"] ?? "";


  //沒有登入資訊跳轉登入頁

  if(!$userId){

    header("Location:../login.php");

    die;

  }

    

  /* =============================================================================

   * = 新增購買記錄

   * =============================================================================

  **/

   

  $purchaseResult = addPurchase($conn, [

      "user_id" => $userId,

      "sticker_id" => $stickerId,

  ]);


  header("Location:../stickerPage.php?sticker=$stickerId&purchase=$purchaseResult");

  die();

}


header("Location:../stickerPage.php");

sql.php裡的addPurchase():

/**

 * 新增購買紀錄

 * 

 * @param  PDO $conn     PDO實體

 * @param  array $data   要新增的購買記錄資料

 * @return boolean       執行結果

 */

function addPurchase($conn, $data = [])

{

  $sql = "insert into `purchases` (`user_id`, `sticker_id`, `created_at`) values (:user_id, :sticker_id, :created_at)";

  $stmt = $conn->prepare($sql);

 

  $purchaseData = [

        'user_id'       => $data['user_id'], /**完成資料綁定 */

        'sticker_id'    => $data['sticker_id'], /**完成資料綁定 */

        'created_at' => $data['created_at'] ?? date('Y-m-d H:i:s'),

  ];


  $result = $stmt->execute($purchaseData);

    

  //回傳執行結果

  return $result;

}


如果無法自動代入,最好也不要讓使用者直接輸入,比較好的方式是使用下拉式選單來選。

<?php


require __DIR__ . '/etc/bootstrap.php';


$users = fetchAllUser($conn); //function in sql.php

$user_options="";

foreach($users as $user){

  $user_options = $user_options."<option value='".$user->id."'>".$user->name."</option>";

}


$stickers = fetchAllStickers($conn); //function in sql.php

$sticker_options="";

foreach($stickers as $sticker){

 $sticker_options = $sticker_options."<option value='".$sticker->id."'>".$sticker->title."</option>";

}


?>

<form action="controllers/purchase_process.php" method ="post">


  user:

  <select name ="userId">

  <?=$user_options?>

  </select>

  sticker:

  <select name ="stickerId">

  <?=$sticker_options?>

  </select>


  <input type="submit" value="Submit"/>


</form>


另外,除了在程式的控制之外,資料庫也會根據建立好的限制式控制資料不一致的問題。例如:

ALTER TABLE `purchases`

  ADD CONSTRAINT `purchase_sticker` FOREIGN KEY (`sticker_id`) REFERENCES `stickers` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,

  ADD CONSTRAINT `purchase_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT;

在一般的資料庫中可利用restrict或cascade來控制,以purchase跟users之間的關聯而言,restrict就是當users的id要更新或刪掉一個使用者時,會先檢查是否在purchases裡有對應資料,如果有,就不准使用者的id被更新或整筆被刪除 (詳參: mysql的foreign key介紹13.1.20.6 FOREIGN KEY Constraints)。cascade則是當users的id要更新時,一併更新purchase裡的user_id,如果users裡的資料被刪除,會一併刪除purchase裡的資料。

目前purchases的設計是相當簡單的,一般而言,會是有個訂單(對使用者是purchase order,對商家是sales order)的概念,每張訂單會有多個產品的。這時候就要需要兩張資料表order_master及order_detail,如果訂單的編號(order_master)是由資料庫自動產的話,那在產生訂單(order_master)之後,要先取得上一筆新增的序號,才能根據這個序號將對應的訂單編號也放到order_detail裡 (詳參:PDO 取得上一筆新增的序號 lastInsertId() 和取得變動的行數數量 rowCount())。