1. 程式人生 > 實用技巧 >MySQL 多表-事務

MySQL 多表-事務

資料庫的設計

一、多表之間的關係

  1. 一對一(瞭解):

    • 如:人和身份證,一個人只有一個身份證,一個身份證只能對應一個人。
    • 實現方式:一對一關係實現,可以在任意一方新增唯一外來鍵指向另一方的主鍵。
    • 將來程式開發時,很少存在。如果是一對一關係,可以直接合併成一張表。
  2. 一對多(多對一):

    • 如:部門和員工,一個部門有多個員工,一個員工只能對應一個部門。
    • 實現方式:在多的一方建立外來鍵,指向一的一方的主鍵。
  3. 多對多:

    • 如:學生和課程,一個學生可以選擇很多門課程,一個課程也可以被很多學生選擇。
    • 實現方式:多對多關係實現需要藉助第三張中間表。中間表至少包含兩個欄位,這兩個欄位作為第三張表的外來鍵,分別指向兩張表的主鍵。
  4. 多對多關係案例

需求:旅遊網,一個使用者收藏多個線路,一個線路被多個使用者收藏。

案例程式碼

-- 建立旅遊線路分類表 tab_category
-- cid 旅遊線路分類主鍵,自動增長
-- cname 旅遊線路分類名稱非空,唯一,字串 100
CREATE TABLE tab_category (
cid INT PRIMARY KEY AUTO_INCREMENT,
cname VARCHAR(100) NOT NULL UNIQUE
);

-- 建立旅遊線路表 tab_route
/*
rid 旅遊線路主鍵,自動增長
rname 旅遊線路名稱非空,唯一,字串 100
price 價格
rdate 上架時間,日期型別
cid 外來鍵,所屬分類
*/
CREATE TABLE tab_route(
rid INT PRIMARY KEY AUTO_INCREMENT,
rname VARCHAR(100) NOT NULL UNIQUE,
price DOUBLE,
rdate DATE,
cid INT,
FOREIGN KEY (cid) REFERENCES tab_category(cid)
);

-- 建立使用者表 tab_user
/*
uid 使用者主鍵,自增長
username 使用者名稱長度 100,唯一,非空
password 密碼長度 30,非空
name 真實姓名長度 100
birthday 生日
sex 性別,定長字串 1
telephone 手機號,字串 11
email 郵箱,字串長度 100
*/
CREATE TABLE tab_user (
uid INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100) UNIQUE NOT NULL,
PASSWORD VARCHAR(30) NOT NULL,
NAME VARCHAR(100),
birthday DATE,
sex CHAR(1) DEFAULT '男',
telephone VARCHAR(11),
email VARCHAR(100)
);

-- 建立收藏表 tab_favorite
/*
rid 旅遊線路 id,外來鍵
date 收藏時間
uid 使用者 id,外來鍵
rid 和 uid 不能重複,設定複合主鍵,同一個使用者不能收藏同一個線路兩次
*/
CREATE TABLE tab_favorite (
rid INT, -- 線路id
DATE DATETIME,
uid INT, -- 使用者id
-- 建立複合主鍵
PRIMARY KEY(rid,uid), -- 聯合主鍵
FOREIGN KEY (rid) REFERENCES tab_route(rid),
FOREIGN KEY(uid) REFERENCES tab_user(uid)
);

二、資料庫設計的正規化

  • 概念:設計資料庫時,需要遵循的一些規範。要遵循後邊的正規化要求,必須先遵循前邊的所有正規化要求

    設計關係資料庫時,遵從不同的規範要求,設計出合理的關係型資料庫,這些不同的規範要求被稱為不同的正規化,**各種正規化呈遞次規範**,越高的正規化資料庫冗餘越小。
    
    目前關係資料庫有六種正規化:第一正規化(1NF)、第二正規化(2NF)、第三正規化(3NF)、巴斯-科德正規化(BCNF)、第四正規化(4NF)和第五正規化(5NF,又稱完美正規化)。
    
    一般遵守前三種正規化,資料庫的設計就沒有什麼問題了。
    
  • 分類

  1. 第一正規化(1NF):每一列都是不可分割的原子資料項,不能是集合、陣列等非原子資料項。即表中的某個列有多個值時必須拆分為不同的列。簡而言之,第一正規化每一列不可再拆分,稱為原子性。

    不符合第一正規化,將列“系”拆分。

    上表存在問題:

         存在非常多的資料冗餘(重複):姓名、系、系主任。  
    
         資料新增存在問題:如新增不合法的系、系主任並不會報錯。
    
         刪除資料時存在問題:如張無忌同學畢業了,刪除資料時,會將系的資料一起刪掉。該系只有一個同學時,表中再無此係資料。
    
  2. 第二正規化(2NF):在1NF的基礎上,非碼屬性必須完全依賴於碼(在1NF基礎上消除非主屬性對主碼的部分函式依賴)。簡而言之,消除部分依賴。

    消除姓名、系、系名稱對(學號,課程)的部分依賴。做一個表的拆分。

  3. 第三正規化(3NF):在2NF基礎上,任何非主屬性不依賴於其它非主屬性(在2NF基礎上消除傳遞依賴)

  • 幾個概念
  1. 函式依賴:A-->B,如果通過A屬性(屬性組)的值,可以確定唯一B屬性的值。則稱B依賴於A
    例如:學號-->姓名。 (學號,課程名稱) --> 分數

  2. 完全函式依賴:A-->B,如果A是一個屬性組,則B屬性值得確定需要依賴於A屬性組中所有的屬性值。
    例如:(學號,課程名稱) --> 分數

  3. 部分函式依賴:A-->B,如果A是一個屬性組,則B屬性值得確定只需要依賴於A屬性組中某一些值即可。
    例如:(學號,課程名稱) --> 姓名

  4. 傳遞函式依賴:A-->B, B-->C. 如果通過 A 屬性(屬性組)的值,可以確定唯一 B 屬性的值,在通過 B 屬性(屬性組)的值可以確定唯一 C 屬性的值,則稱 C 傳遞函式依賴於 A。
    例如:學號-->系名,系名-->系主任

  5. 碼:如果在一張表中,一個屬性或屬性組,被其他所有屬性所完全依賴,則稱這個屬性(屬性組)為該表的碼。
    例如:該表中碼為:(學號,課程名稱)

  6. 主屬性:碼屬性組中的所有屬性

  7. 非主屬性:除 碼屬性組的屬性

三、資料庫的備份和還原

  • 備份: mysqldump -u使用者名稱 -p密碼 資料庫名稱 > 儲存的路徑

  • 還原:

    1. 登入資料庫 `mysql -uroot -p密碼`
    
    2. 建立資料庫 `create database db1;`
    
    3. 使用資料庫 `use db1;`
    
    4. 執行檔案。`source 檔案路徑` 如`source d://a.sql`
    

四、多表查詢

建立表

-- 建立部門表
CREATE TABLE dept(
      id INT PRIMARY KEY AUTO_INCREMENT,
      NAME VARCHAR(20)
);
INSERT INTO dept (NAME) VALUES ('開發部'),('市場部'),('財務部');
-- 建立員工表
CREATE TABLE emp (
      id INT PRIMARY KEY AUTO_INCREMENT,
      NAME VARCHAR(10),
      gender CHAR(1), -- 性別
      salary DOUBLE, -- 工資
      join_date DATE, -- 入職日期
      dept_id INT,
      FOREIGN KEY (dept_id) REFERENCES dept(id) -- 外來鍵,關聯部門表(部門表的主鍵)
);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('孫悟空','男',7200,'2013-02-24',1);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('豬八戒','男',3600,'2010-12-02',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('唐僧','男',9000,'2008-08-08',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('白骨精','女',5000,'2015-10-07',3);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('蜘蛛精','女',4500,'2011-03-14',1);

笛卡爾積:有兩個集合A、B,取這兩個集合的所有組成情況。

  • 如上述兩個例子中,select * from dept, emp; 會返回 3*5=15 種情況,很多是錯誤的組合。要完成多表查詢,需要消除無用的資料。

4.1 內連線查詢

隱式內連線:使用where條件消除無用資料

-- 查詢所有員工資訊和對應的部門資訊
SELECT * FROM emp,dept WHERE emp.`dept_id` = dept.`id`;

-- 查詢員工表的名稱,性別,部門表的名稱
SELECT emp.name,emp.gender,dept.name FROM emp,dept WHERE emp.`dept_id` = dept.`id`;

-- 使用別名
SELECT
      t1.name, -- 員工表的姓名
      t1.gender,-- 員工表的性別
      t2.name -- 部門表的名稱
FROM
      emp t1, 
      dept t2
WHERE
      t1.`dept_id` = t2.`id`;


顯式內連線select 欄位列表 from 表名1 [inner] join 表名2 on 條件

SELECT * FROM emp INNER JOIN dept ON emp.`dept_id` = dept.`id`;
SELECT * FROM emp JOIN dept ON emp.`dept_id` = dept.`id`;

隱式與顯式內連線查詢結果一樣,只是寫法不同。

內連線查詢考慮:從哪些表中查詢資料、條件是什麼、查詢哪些欄位。

4.2 外連結查詢

左外連線:查詢的是左表所有資料以及其交集部分。

select 欄位列表 from 表1 left [outer] join 表2 on 條件;

-- 查詢所有員工資訊,如果員工有部門,則查詢部門名稱,沒有部門,則不顯示部門名稱
SELECT 	t1.*,t2.`name` FROM emp t1 LEFT JOIN dept t2 ON t1.`dept_id` = t2.`id`;

右外連線:查詢的是右表所有資料以及其交集部分。

select 欄位列表 from 表1 right [outer] join 表2 on 條件;

SELECT 	* FROM dept t2 RIGHT JOIN emp t1 ON t1.`dept_id` = t2.`id`;

4.3 子查詢

  • 概念:查詢中巢狀查詢,稱巢狀查詢為子查詢。

    -- 查詢工資最高的員工資訊
    
    -- 1 查詢最高的工資是多少 9000
    SELECT MAX(salary) FROM emp;
    
    -- 2 查詢員工資訊,並且工資等於9000的
    SELECT * FROM emp WHERE emp.`salary` = 9000;
    
    -- 一條sql就完成這個操作。子查詢
    SELECT * FROM emp WHERE emp.`salary` = (SELECT MAX(salary) FROM emp);
    
  • 子查詢不同情況

    1. 子查詢的結果是單行單列的:子查詢可以作為條件,使用運算子去判斷。 【運算子: > >= < <= =】
    
    -- 查詢員工工資小於平均工資的人
    SELECT * FROM emp WHERE emp.salary < (SELECT AVG(salary) FROM emp);
    
    
    2. 子查詢的結果是多行單列的:子查詢可以作為條件,使用【運算子in】來判斷
    
    -- 查詢'財務部'和'市場部'所有的員工資訊
    SELECT id FROM dept WHERE NAME = '財務部' OR NAME = '市場部'; -- id 返回 2 與 3
    SELECT * FROM emp WHERE dept_id = 3 OR dept_id = 2;
    -- 子查詢
    SELECT * FROM emp WHERE dept_id IN (SELECT id FROM dept WHERE NAME = '財務部' OR NAME = '市場部');
    
    
    3. 子查詢的結果是多行多列的:子查詢可以作為一張虛擬表參與查詢
    
    -- 查詢員工入職日期是2011-11-11日之後的員工資訊和部門資訊
    -- 子查詢
    SELECT * FROM dept t1 ,(SELECT * FROM emp WHERE emp.`join_date` > '2011-11-11') t2
    WHERE t1.id = t2.dept_id;
    
    -- 普通內連線也可以實現
    SELECT * FROM emp t1,dept t2 WHERE t1.`dept_id` = t2.`id` AND t1.`join_date` >  '2011-11-11';
    

4.4 多表查詢練習

展示程式碼

-- 部門表
CREATE TABLE dept (
      id INT PRIMARY KEY PRIMARY KEY, -- 部門id
      dname VARCHAR(50), -- 部門名稱
      loc VARCHAR(50) -- 部門所在地
);
-- 新增4個部門
INSERT INTO dept(id,dname,loc) VALUES
(10,'教研部','北京'),
(20,'學工部','上海'),
(30,'銷售部','廣州'),
(40,'財務部','深圳');
-- 
-- 職務表,職務名稱,職務描述
CREATE TABLE job (
      id INT PRIMARY KEY,
      jname VARCHAR(20),
      description VARCHAR(50)
);
-- 新增4個職務
INSERT INTO job (id, jname, description) VALUES
(1, '董事長', '管理整個公司,接單'),
(2, '經理', '管理部門員工'),
(3, '銷售員', '向客人推銷產品'),
(4, '文員', '使用辦公軟體');
-- 
-- 員工表
CREATE TABLE emp (
      id INT PRIMARY KEY, -- 員工id
      ename VARCHAR(50), -- 員工姓名
      job_id INT, -- 職務id
      mgr INT , -- 上級領導
      joindate DATE, -- 入職日期
      salary DECIMAL(7,2), -- 工資
      bonus DECIMAL(7,2), -- 獎金
      dept_id INT, -- 所在部門編號
      CONSTRAINT emp_jobid_ref_job_id_fk FOREIGN KEY (job_id) REFERENCES job (id),
      CONSTRAINT emp_deptid_ref_dept_id_fk FOREIGN KEY (dept_id) REFERENCES dept (id)
);
-- 新增員工
INSERT INTO emp(id,ename,job_id,mgr,joindate,salary,bonus,dept_id) VALUES
(1001,'孫悟空',4,1004,'2000-12-17','8000.00',NULL,20),
(1002,'盧俊義',3,1006,'2001-02-20','16000.00','3000.00',30),
(1003,'林沖',3,1006,'2001-02-22','12500.00','5000.00',30),
(1004,'唐僧',2,1009,'2001-04-02','29750.00',NULL,20),
(1005,'李逵',4,1006,'2001-09-28','12500.00','14000.00',30),
(1006,'宋江',2,1009,'2001-05-01','28500.00',NULL,30),
(1007,'劉備',2,1009,'2001-09-01','24500.00',NULL,10),
(1008,'豬八戒',4,1004,'2007-04-19','30000.00',NULL,20),
(1009,'羅貫中',1,NULL,'2001-11-17','50000.00',NULL,10),
(1010,'吳用',3,1006,'2001-09-08','15000.00','0.00',30),
(1011,'沙僧',4,1004,'2007-05-23','11000.00',NULL,20),
(1012,'李逵',4,1006,'2001-12-03','9500.00',NULL,30),
(1013,'小白龍',4,1004,'2001-12-03','30000.00',NULL,20),
(1014,'關羽',4,1007,'2002-01-23','13000.00',NULL,10);
-- 
-- 工資等級表
CREATE TABLE salarygrade (
      grade INT PRIMARY KEY,   -- 級別
      losalary INT,  -- 最低工資
      hisalary INT -- 最高工資
);
-- 新增5個工資等級
INSERT INTO salarygrade(grade,losalary,hisalary) VALUES
(1,7000,12000),
(2,12010,14000),
(3,14010,20000),
(4,20010,30000),
(5,30010,99990);

幾個需求

  1. 查詢所有員工資訊。查詢員工編號,員工姓名,工資,職務名稱,職務描述
/*
  分析:
      1)員工編號,員工姓名,工資,需要查詢emp表  職務名稱,職務描述 需要查詢job表
      2)查詢條件 emp.job_id = job.id
*/

SELECT
      t1.`id`, -- 員工編號
      t1.`ename`, -- 員工姓名
      t1.`salary`,-- 工資
      t2.`jname`, -- 職務名稱
      t2.`description` -- 職務描述
FROM
      emp t1, job t2
WHERE
      t1.`job_id` = t2.`id`;


2. 查詢員工編號,員工姓名,工資,職務名稱,職務描述,部門名稱,部門位置

/*
  分析:
      1. 員工編號,員工姓名,工資 emp  職務名稱,職務描述 job  部門名稱,部門位置 dept
      2. 條件: emp.job_id = job.id and emp.dept_id = dept.id
*/

SELECT
      t1.`id`, -- 員工編號
      t1.`ename`, -- 員工姓名
      t1.`salary`,-- 工資
      t2.`jname`, -- 職務名稱
      t2.`description`, -- 職務描述
      t3.`dname`, -- 部門名稱
      t3.`loc` -- 部門位置
FROM
      emp t1, job t2,dept t3
WHERE
      t1.`job_id` = t2.`id` AND t1.`dept_id` = t3.`id`;
  1. 查詢員工姓名,工資,工資等級
/*
  分析:
      1.員工姓名,工資 emp  工資等級 salarygrade
      2.條件 emp.salary >= salarygrade.losalary and emp.salary <= salarygrade.hisalary
            emp.salary BETWEEN salarygrade.losalary and salarygrade.hisalary
*/

SELECT
      t1.ename ,
      t1.`salary`,
      t2.grade
FROM 
      emp t1, salarygrade t2
WHERE 
      t1.`salary` BETWEEN t2.`losalary` AND t2.`hisalary`;


4. 查詢員工姓名,工資,職務名稱,職務描述,部門名稱,部門位置,工資等級

/*
  分析:
      1. 員工姓名,工資 emp , 職務名稱,職務描述 job 部門名稱,部門位置,dept  工資等級 salarygrade
      2. 條件: emp.job_id = job.id and emp.dept_id = dept.id and emp.salary BETWEEN salarygrade.losalary and salarygrade.hisalary
*/

SELECT
      t1.`ename`,
      t1.`salary`,
      t2.`jname`,
      t2.`description`,
      t3.`dname`,
      t3.`loc`,
      t4.`grade`
FROM
      emp t1,job t2,dept t3,salarygrade t4
WHERE
      t1.`job_id` = t2.`id`
AND 
      t1.`dept_id` = t3.`id`
AND 
      t1.`salary` BETWEEN t4.`losalary` AND t4.`hisalary`;



5. 查詢出部門編號、部門名稱、部門位置、部門人數