课程 › 第 11 章 › 实验二
第 11 章 · 实验二 · 在第五节之后

照着数据字典建表

把课件第 23 页数据字典中的数据结构逐个建成表,装入课件第 13 页的订单,发现订单的“销售商品”一项放不下多件商品,再拆出订单明细表,把整张订单查回来。

环 境

MySQL 8.0 和 MySQL Workbench,机房电脑已装好。所有语句在 Workbench 的 SQL 编辑区中执行。

执 行

Ctrl+Enter 执行光标所在的一句,Ctrl+Shift+Enter 执行全部。Mac 上把 Ctrl 换成 ⌘。

注 意

输入法切到英文。表名、字段名不加引号,字符串值才用单引号。

本次实验的顺序 后一个实验用到前一个的结果

  1. 实验 2-1用户信息和登录凭证
  2. 实验 2-2收货地址
  3. 实验 2-3系统订单
  4. 实验 2-4销售商品一栏的问题
  5. 实验 2-5拆出订单明细表
  6. 实验 2-6把订单查回来
第一步

数据准备(必做)

重建 shop 库,装入实验一拆好的供应商表 supplier 和商品表 goods。数据字典中的“商品库存”就是这张 goods 表。

整段复制,全部执行下载 .sql
-- 第 11 章实验二数据:实验一拆好的供应商表 supplier 和商品表 goods
-- 整段执行一次即可,想从头再做时重新执行。shop 库会被重建,实验一的台账表不再保留。
DROP DATABASE IF EXISTS shop;
CREATE DATABASE shop DEFAULT CHARACTER SET utf8mb4;
USE shop;

-- 供应商表
CREATE TABLE supplier (
  sid INT AUTO_INCREMENT,
  sname VARCHAR(30) NOT NULL,
  sphone CHAR(11),
  PRIMARY KEY (sid),
  UNIQUE (sname)
);
-- 商品库存表,sid 指向供应商
CREATE TABLE goods (
  gno CHAR(10) NOT NULL,
  gname VARCHAR(40),
  gtype VARCHAR(10),
  price DECIMAL(8,2),
  maker VARCHAR(30),
  intime DATETIME,
  qty INT,
  sid INT,
  PRIMARY KEY (gno),
  FOREIGN KEY (sid) REFERENCES supplier (sid)
);

INSERT INTO supplier (sid, sname, sphone) VALUES
(1, '华信图书公司', '18600009999'), (2, '东方数码公司', '13900001234'), (3, '远达文具公司', '13600007788');
INSERT INTO goods VALUES
('1100000112', '数据库原理及应用教程', '图书', 45.50, '人民邮电出版社', '2020-02-11 11:15:25', 100, 1),
('1100000118', 'MySQL 数据库实用教程', '图书', 39.80, '人民邮电出版社', '2020-02-11 11:20:10', 80, 1),
('2120000109', '联想笔记本电脑 E14', '电脑', 5588.60, '联想集团', '2020-03-02 09:30:00', 20, 2),
('2120000110', '联想无线鼠标', '配件', 89.00, '联想集团', '2020-03-02 09:35:00', 50, 2),
('3300000201', '得力中性笔', '文具', 2.50, '得力集团', '2020-03-05 14:00:00', 500, 3);

SHOW TABLES;
2-1

用户信息和登录凭证

讲义 · 案例的数据字典
目的
按数据字典中的“用户信息”和“登录凭证”,建立用户表 users 和登录记录表 login_record。
前提
已执行数据准备,当前库是 shop。
字段含义数据类型要求
uname登录用户名VARCHAR(20)主码
upwd登录密码VARCHAR(64)不能为空
email找回密码的邮箱VARCHAR(50)
login_time登录时间DATETIME在 login_record 中
  1. 建立用户表,存用户名、密码、找回密码用的邮箱
  2. 建立登录记录表,存谁在什么时间登录
  3. 各插入一行,查看两张表

参考答案

可以直接复制到 Workbench 执行
CREATE TABLE users (
  uname VARCHAR(20) NOT NULL,
  upwd VARCHAR(64) NOT NULL,
  email VARCHAR(50),
  PRIMARY KEY (uname)
);
-- 登录凭证:谁在什么时间登录成功
CREATE TABLE login_record (
  lid INT AUTO_INCREMENT,
  uname VARCHAR(20) NOT NULL,
  login_time DATETIME,
  PRIMARY KEY (lid),
  FOREIGN KEY (uname) REFERENCES users (uname)
);
INSERT INTO users VALUES ('lina', '123456', 'lina@163.com');
INSERT INTO login_record (uname, login_time)
VALUES ('lina', '2020-04-10 16:58:02');
SELECT * FROM users;
SELECT * FROM login_record;

语句解释

users数据字典中的“用户信息”:用户名、密码、忘记密码邮箱
login_record数据字典中的“登录凭证”:用户名、登录时间。一个用户可以登录多次,所以单独一张表
lid登录记录的编号,自动增长
upwd这里为了看得清楚存的是明文,实际系统中密码要加密后再存
执行后看:users 中 1 行,login_record 中 1 行,登录记录通过 uname 对应到用户 lina。
2-2

收货地址

讲义 · 案例的数据字典
目的
按数据字典中的“收货地址”建立地址表 address,一个用户可以有多个收货地址。
前提
已完成实验 2-1。
  1. 建立地址表,每个地址有编号,并记下属于哪个用户
  2. 按课件第 13 页的订单,插入李娜的收货地址

参考答案

可以直接复制到 Workbench 执行
CREATE TABLE address (
  aid INT AUTO_INCREMENT,
  uname VARCHAR(20) NOT NULL,
  contact VARCHAR(20),
  sex CHAR(1),
  addr VARCHAR(60),
  phone CHAR(11),
  email VARCHAR(50),
  PRIMARY KEY (aid),
  FOREIGN KEY (uname) REFERENCES users (uname)
);
INSERT INTO address (uname, contact, sex, addr, phone, email)
VALUES ('lina', '李娜', '女', '北京市海淀区成府路 100 号',
        '13800002876', 'lina@163.com');
SELECT * FROM address;

语句解释

contact、sex、addr、phone、email数据字典中的联系人、性别、收货地址、手机、电子邮箱
uname外码,这个地址属于哪个用户
aid地址编号。同一个用户的几个地址靠它区分,订单也用它指明寄到哪里
执行后看:address 中 1 行,aid 为 1。
2-3

系统订单

讲义 · 案例的数据字典
目的
按数据字典中的“系统订单”建立订单表 orders,先照字典原样保留“销售商品”一项,装入课件第 13 页的订单。
前提
已完成实验 2-2。
  1. 建立订单表,订单指向用户和收货地址
  2. “销售商品”用一个文字字段 goods_list 存放
  3. 插入课件第 13 页的订单

参考答案

可以直接复制到 Workbench 执行
CREATE TABLE orders (
  ono CHAR(17) NOT NULL,
  uname VARCHAR(20),
  aid INT,
  submit_time DATETIME,
  total DECIMAL(10,2),
  status VARCHAR(10),
  logistics VARCHAR(40),
  goods_list VARCHAR(100),
  PRIMARY KEY (ono),
  FOREIGN KEY (uname) REFERENCES users (uname),
  FOREIGN KEY (aid) REFERENCES address (aid)
);
-- 一张订单买了两种商品,都写在 goods_list 中
INSERT INTO orders VALUES
('20200410140000001', 'lina', 1, '2020-04-10 17:03:28', 11404.70,
 '已完成', '中国邮政快递 5650000001',
 '1100000112 共 5 件,2120000109 共 2 件');
SELECT * FROM orders;

语句解释

ono订单编号,17 位数字,用 CHAR(17)
aid外码,订单寄到哪个收货地址
total订单总计 11404.70,即 5 × 45.50 + 2 × 5588.60
goods_list数据字典中的“销售商品”,这里先照原样写成一段文字
执行后看:orders 中 1 行,goods_list 一栏写着两种商品和各自的件数。
2-4

销售商品一栏的问题

讲义 · 案例的数据字典
目的
用订单表回答“数据库原理及应用教程一共卖出了几本”,体会把几件商品写在一个字段中的问题。
前提
已完成实验 2-3。
  1. 查出含有商品 1100000112 的订单
  2. 想一想,怎样用 SQL 算出这种商品一共卖了几件

参考答案

可以直接复制到 Workbench 执行
-- 只能用 LIKE 在文字中找,找到了也算不出件数
SELECT ono, goods_list FROM orders
WHERE goods_list LIKE '%1100000112%';

语句解释

LIKE按文字查找,能找到订单,但件数写在文字中间,SUM 算不出来
一个字段放了几件商品字段的值还能再分,下一章会讲,这样的表连第一范式都不满足
解决办法一张订单有几种商品,就在另一张表中存几行
执行后看:查到这张订单,但 goods_list 是一段文字,没有办法对其中的件数求和,也不能让 1100000112 参照商品表。
2-5

拆出订单明细表

讲义 · 案例的数据字典
目的
建立订单明细表 order_item,一张订单的每种商品存一行,再删掉订单表中的 goods_list。
前提
已完成实验 2-4。
  1. 建立订单明细表,主码由订单编号和商品编号组成
  2. 插入这张订单的两种商品
  3. 删除 orders 中的 goods_list 字段

参考答案

可以直接复制到 Workbench 执行
CREATE TABLE order_item (
  ono CHAR(17) NOT NULL,
  gno CHAR(10) NOT NULL,
  qty INT,
  price DECIMAL(8,2),
  PRIMARY KEY (ono, gno),
  FOREIGN KEY (ono) REFERENCES orders (ono),
  FOREIGN KEY (gno) REFERENCES goods (gno)
);
INSERT INTO order_item VALUES
('20200410140000001', '1100000112', 5, 45.50),
('20200410140000001', '2120000109', 2, 5588.60);
-- 商品已经存到明细表中,这一栏不再需要
ALTER TABLE orders DROP COLUMN goods_list;
SELECT * FROM order_item;

语句解释

PRIMARY KEY (ono, gno)同一张订单中,同一种商品只有一行
FOREIGN KEY (gno)明细中的商品必须是商品表中存在的商品
price成交时的单价。商品以后调价,已经成交的订单不受影响
ALTER TABLE … DROP COLUMN删除订单表中的 goods_list 字段
执行后看:order_item 中 2 行。orders 中不再有 goods_list。
2-6

把订单查回来

讲义 · 案例的数据字典
目的
把订单、收货地址、订单明细和商品四张表连接起来,查出课件第 13 页那张订单的全部内容,并核对订单总计。
前提
已完成实验 2-5。
  1. 查出订单编号、提交时间、联系人、收货地址、商品名称、数量和单价
  2. 按明细算出订单总计
  3. 统计商品 1100000112 一共卖出几件

参考答案

可以直接复制到 Workbench 执行
SELECT o.ono, o.submit_time, a.contact, a.addr,
       g.gname, i.qty, i.price
FROM orders o
JOIN address a ON o.aid = a.aid
JOIN order_item i ON o.ono = i.ono
JOIN goods g ON i.gno = g.gno;
-- 按明细算出的总计,应与 orders 中的 total 相同
SELECT ono, SUM(qty * price) AS 明细合计
FROM order_item GROUP BY ono;
SELECT SUM(qty) AS 卖出件数
FROM order_item WHERE gno = '1100000112';

语句解释

四张表连接订单 → 地址、订单 → 明细 → 商品,每一步用外码对上
SUM(qty * price)每行的数量乘单价,再加起来
SUM(qty)实验 2-4 算不出来的件数,现在一句就能算出
执行后看:第一个查询 2 行,两行的订单信息相同,商品不同。明细合计为 11404.70,与 total 一致。卖出件数为 5。
保山学院·人工智能教研室·曹鼎鼎