據(jù)庫(kù)課設(shè)實(shí)戰(zhàn):高校學(xué)籍管理系統(tǒng)SQL修正與觸發(fā)器實(shí)現(xiàn))
簡(jiǎn)介這份數(shù)據(jù)庫(kù)原理課程設(shè)計(jì)報(bào)告以某高校學(xué)籍管理系統(tǒng)為完整案例面向計(jì)算機(jī)相關(guān)專(zhuān)業(yè)學(xué)生及數(shù)據(jù)庫(kù)初學(xué)者幫助讀者掌握從需求分析到物理設(shè)計(jì)的全流程實(shí)踐方法。報(bào)告圍繞學(xué)生信息、班級(jí)、課程、成績(jī)與獎(jiǎng)懲五大管理模塊展開(kāi)涵蓋概念結(jié)構(gòu)、邏輯結(jié)構(gòu)與物理結(jié)構(gòu)設(shè)計(jì)并給出學(xué)生表、班級(jí)表、課程表、成績(jī)表、用戶(hù)表及獎(jiǎng)懲表的主外鍵定義與參照完整性約束方案。資源包內(nèi)含1個(gè)doc文檔壓縮后約440KB結(jié)構(gòu)緊湊便于直接查閱與二次修改。其中還涉及視圖創(chuàng)建、存儲(chǔ)過(guò)程統(tǒng)計(jì)各院系專(zhuān)業(yè)男女人數(shù)、觸發(fā)器自動(dòng)更新班級(jí)人數(shù)、check約束限制性別輸入等具體實(shí)現(xiàn)并附有主要功能程序段與模塊流程圖。目前已有1094人學(xué)習(xí)下載適合用作課程設(shè)計(jì)參考、實(shí)驗(yàn)報(bào)告模板或數(shù)據(jù)庫(kù)綜合練習(xí)的對(duì)照材料。1. 一份能跑通的學(xué)籍管理系統(tǒng)課設(shè)報(bào)告到底長(zhǎng)什么樣如果你正在搜「數(shù)據(jù)庫(kù)原理課程設(shè)計(jì)報(bào)告 某高校的學(xué)籍管理系統(tǒng)」大概率是三種處境之一課設(shè) deadline 逼近手里只有題目沒(méi)有可參考的完整實(shí)現(xiàn)想照著別人的報(bào)告復(fù)現(xiàn)一遍結(jié)果發(fā)現(xiàn) SQL 語(yǔ)句全是語(yǔ)法錯(cuò)誤或者你已經(jīng)寫(xiě)完了但觸發(fā)器、存儲(chǔ)過(guò)程、參照完整性這幾塊心里沒(méi)底想找個(gè)能對(duì)標(biāo)的版本核一遍。這份資源就是一份完整的數(shù)據(jù)庫(kù)原理課程設(shè)計(jì)報(bào)告主題是高校學(xué)籍管理系統(tǒng)覆蓋學(xué)生信息、班級(jí)、課程、成績(jī)、獎(jiǎng)懲五個(gè)業(yè)務(wù)模塊技術(shù)棧是 SQL 數(shù)據(jù)庫(kù)報(bào)告里寫(xiě)的是 SQL 軟件 Windows XP 環(huán)境核心交付物包括建庫(kù)建表語(yǔ)句、視圖定義、觸發(fā)器、存儲(chǔ)過(guò)程、CHECK 約束以及參照完整性設(shè)計(jì)。它適合數(shù)據(jù)庫(kù)課程的本科生做課設(shè)參考也適合剛接觸 SQL 的開(kāi)發(fā)者拿來(lái)當(dāng)?shù)谝粋€(gè)「從需求到建表到寫(xiě)觸發(fā)器」的練手項(xiàng)目。需要先說(shuō)明一點(diǎn)報(bào)告原文里的 SQL 語(yǔ)句存在不少拼寫(xiě)和語(yǔ)法問(wèn)題比如Create datebase、Creat table、PBIMARY、字段類(lèi)型寫(xiě)成Date(8)這種不存在的寫(xiě)法。所以這份資源的價(jià)值不在于「復(fù)制粘貼就能跑」而在于它給出了一套完整的數(shù)據(jù)庫(kù)設(shè)計(jì)思路和模塊劃分你需要在此基礎(chǔ)上把 SQL 修正到能執(zhí)行的狀態(tài)。下面我會(huì)按「設(shè)計(jì)邏輯 → 建表實(shí)操 → 視圖/觸發(fā)器/存儲(chǔ)過(guò)程 → 避坑 → 進(jìn)階驗(yàn)證」的順序拆一遍讓你拿到手就能改、改完就能跑。2. 從需求到關(guān)系模式學(xué)籍管理系統(tǒng)的表結(jié)構(gòu)怎么定2.1 五個(gè)核心實(shí)體和它們的關(guān)系報(bào)告里把系統(tǒng)拆成了學(xué)生、班級(jí)、課程、成績(jī)、獎(jiǎng)懲、用戶(hù)六個(gè)實(shí)體其中用戶(hù)表是給登錄模塊用的跟業(yè)務(wù)數(shù)據(jù)關(guān)系不大。真正需要理清關(guān)系的是前五個(gè)。學(xué)生和班級(jí)是多對(duì)一一個(gè)學(xué)生屬于一個(gè)班級(jí)一個(gè)班級(jí)有多個(gè)學(xué)生。學(xué)生和課程是多對(duì)多中間通過(guò)成績(jī)表來(lái)關(guān)聯(lián)——成績(jī)表里同時(shí)出現(xiàn)學(xué)號(hào)和課程名這就是典型的關(guān)聯(lián)表設(shè)計(jì)。獎(jiǎng)懲表跟學(xué)生是一對(duì)多一個(gè)學(xué)生可以有多條獎(jiǎng)懲記錄。課程和班級(jí)之間報(bào)告里課程表有classname字段暗示課程是跟班級(jí)綁定的這個(gè)設(shè)計(jì)在實(shí)際選課場(chǎng)景下會(huì)有問(wèn)題后面避坑章節(jié)會(huì)展開(kāi)說(shuō)。邏輯結(jié)構(gòu)設(shè)計(jì)里報(bào)告給出了關(guān)系模式學(xué)生姓名性別學(xué)號(hào)班級(jí)號(hào)專(zhuān)業(yè)出生日期入學(xué)日期家庭住址備注學(xué)號(hào)為主鍵班級(jí)班級(jí)號(hào)年級(jí)輔導(dǎo)員自習(xí)室班級(jí)號(hào)為主鍵課程課程名課程編號(hào)課程類(lèi)型課程描述課程編號(hào)為主鍵成績(jī)學(xué)生姓名學(xué)號(hào)班級(jí)名稱(chēng)課程名分?jǐn)?shù)學(xué)號(hào)課程名聯(lián)合主鍵獎(jiǎng)懲姓名學(xué)號(hào)年級(jí)輔導(dǎo)員學(xué)號(hào)為主鍵用戶(hù)用戶(hù)名密碼權(quán)限用戶(hù)名為主鍵這里有個(gè)細(xì)節(jié)值得注意成績(jī)表用「學(xué)號(hào)課程名」做聯(lián)合主鍵而不是用課程編號(hào)。用課程名做外鍵的問(wèn)題是課程名可能重復(fù)或修改參照完整性會(huì)很脆弱。常見(jiàn)做法是成績(jī)表里存學(xué)號(hào)和課程編號(hào)通過(guò)課程編號(hào)關(guān)聯(lián)課程表。但報(bào)告原文用的是課程名你在復(fù)現(xiàn)時(shí)可以根據(jù)自己的需求決定是否改成課程編號(hào)。2.2 物理設(shè)計(jì)里的字段類(lèi)型選擇報(bào)告給出了六張表的詳細(xì)字段定義我整理成一張對(duì)照表方便你直接照著建表表名字段報(bào)告中的類(lèi)型建議修正類(lèi)型說(shuō)明studentsnoChar(15)CHAR(15)學(xué)號(hào)主鍵studentsnameChar(8)VARCHAR(20)姓名studentssexChar(2)CHAR(2)性別加 CHECK 約束studentscardChar(20)CHAR(18)身份證號(hào)studentsaddreeDate(8)DATE出生日期原文類(lèi)型有誤studentsspecialityChar(20)VARCHAR(30)專(zhuān)業(yè)studentclassnameChar(10)VARCHAR(20)班級(jí)studentstelphoneChar(15)VARCHAR(15)聯(lián)系電話(huà)studentsmemoChar(50)VARCHAR(100)備注classclassnameChar(10)VARCHAR(20)班級(jí)名主鍵classtdepartmentChar(20)VARCHAR(30)所屬院系classtnameChar(8)VARCHAR(20)輔導(dǎo)員classclassroomChar(15)VARCHAR(20)教室classnumberChar(3)INT人數(shù)觸發(fā)器要加減必須用數(shù)值類(lèi)型coursecidChar(10)CHAR(10)課程編號(hào)主鍵coursecoursenameChar(20)VARCHAR(30)課程名courseclassnameChar(20)VARCHAR(20)所屬班級(jí)courseperiodInt(4)INT學(xué)時(shí)coursetidChar(20)CHAR(10)教師編號(hào)scoresnoChar(15)CHAR(15)學(xué)號(hào)聯(lián)合主鍵scorecoursenameChar(10)VARCHAR(30)課程名聯(lián)合主鍵scorescoreInt(4)INT分?jǐn)?shù)jcsnameChar(20)VARCHAR(20)姓名主鍵jcgradeChar(20)VARCHAR(20)年級(jí)jcjdateDate(8)DATE獎(jiǎng)懲日期jcjnameChar(20)VARCHAR(30)獎(jiǎng)懲名稱(chēng)jcjmemoChar(20)VARCHAR(100)備注這張表里最關(guān)鍵的修正有兩處一是class.number必須從Char(3)改成INT因?yàn)橛|發(fā)器里要對(duì)它做加減運(yùn)算字符類(lèi)型做減法會(huì)直接報(bào)錯(cuò)二是所有Date(8)都要改成DATE標(biāo)準(zhǔn) SQL 里沒(méi)有帶精度的日期類(lèi)型。這兩處不改后面的觸發(fā)器和存儲(chǔ)過(guò)程一定跑不起來(lái)。提示如果你用的是 MySQLCHAR和VARCHAR的區(qū)別在于 CHAR 是定長(zhǎng)、VARCHAR 是變長(zhǎng)。學(xué)號(hào)、身份證號(hào)這種長(zhǎng)度固定的用 CHAR姓名、備注這種長(zhǎng)度不固定的用 VARCHAR能省存儲(chǔ)空間。3. 建庫(kù)建表實(shí)操把報(bào)告里的 SQL 修到能執(zhí)行3.1 建庫(kù)和建表語(yǔ)句的修正版報(bào)告原文的建庫(kù)語(yǔ)句是Create datebase student;這里datebase拼錯(cuò)了正確寫(xiě)法是DATABASE。建表語(yǔ)句里Creat table少了Eprimary key寫(xiě)成了primary keys字段之間缺少逗號(hào)分隔。下面是我修正后的完整建表腳本以 MySQL 語(yǔ)法為準(zhǔn)SQL Server 用戶(hù)把AUTO_INCREMENT去掉即可-- 建庫(kù) CREATE DATABASE student_db DEFAULT CHARACTER SET utf8mb4; USE student_db; -- 學(xué)生信息表 CREATE TABLE student ( sno CHAR(15) NOT NULL, sname VARCHAR(20) NOT NULL, ssex CHAR(2) NOT NULL, scard CHAR(18), sbirthday DATE, sspeciality VARCHAR(30), classname VARCHAR(20), stelphone VARCHAR(15), saddress VARCHAR(100), smemo VARCHAR(100), PRIMARY KEY (sno), CONSTRAINT chk_sex CHECK (ssex IN (男, 女)) ); -- 班級(jí)信息表 CREATE TABLE class ( classname VARCHAR(20) NOT NULL, tdepartment VARCHAR(30), tname VARCHAR(20), classroom VARCHAR(20), number INT DEFAULT 0, PRIMARY KEY (classname) ); -- 課程信息表 CREATE TABLE course ( cid CHAR(10) NOT NULL, coursename VARCHAR(30) NOT NULL, classname VARCHAR(20), termname VARCHAR(20), period INT, tid CHAR(10), classroomname VARCHAR(20), coursememo VARCHAR(100), PRIMARY KEY (cid) ); -- 成績(jī)信息表 CREATE TABLE score ( sno CHAR(15) NOT NULL, coursename VARCHAR(30) NOT NULL, sname VARCHAR(20), grade VARCHAR(20), score INT, PRIMARY KEY (sno, coursename) ); -- 獎(jiǎng)懲信息表 CREATE TABLE jc ( sname VARCHAR(20) NOT NULL, grade VARCHAR(20), jdate DATE, jname VARCHAR(30), jmemo VARCHAR(100), PRIMARY KEY (sname, jdate) ); -- 用戶(hù)表 CREATE TABLE user ( username CHAR(20) NOT NULL, password CHAR(20) NOT NULL, role CHAR(10) DEFAULT student, PRIMARY KEY (username) );這段腳本跟報(bào)告原文的差異主要有四處第一所有拼寫(xiě)錯(cuò)誤修正第二student表里把saddree拆成了sbirthday出生日期和saddress家庭住址因?yàn)樵陌褍蓚€(gè)語(yǔ)義完全不同的字段混用了一個(gè)名字第三class.number改成INT并給了默認(rèn)值 0第四jc表的主鍵從單獨(dú)的sname改成sname jdate聯(lián)合主鍵因?yàn)橐粋€(gè)學(xué)生可能有多條獎(jiǎng)懲記錄只用姓名做主鍵會(huì)導(dǎo)致第二條記錄插不進(jìn)去。3.2 參照完整性約束怎么加報(bào)告里提到了「建立數(shù)據(jù)庫(kù)相關(guān)表之間的參照完整性約束」但原文的建表語(yǔ)句里一個(gè)外鍵都沒(méi)寫(xiě)。參照完整性說(shuō)白了就是外鍵約束保證學(xué)生表里的班級(jí)名在班級(jí)表里一定存在成績(jī)表里的學(xué)號(hào)在學(xué)生表里一定存在。加外鍵有兩種方式建表時(shí)用FOREIGN KEY子句或者建表后用ALTER TABLE追加。我一般推薦建完表統(tǒng)一加方便調(diào)整順序-- 學(xué)生表的班級(jí)名參照班級(jí)表 ALTER TABLE student ADD CONSTRAINT fk_student_class FOREIGN KEY (classname) REFERENCES class(classname) ON UPDATE CASCADE ON DELETE SET NULL; -- 成績(jī)表的學(xué)號(hào)參照學(xué)生表 ALTER TABLE score ADD CONSTRAINT fk_score_student FOREIGN KEY (sno) REFERENCES student(sno) ON DELETE CASCADE; -- 成績(jī)表的課程名參照課程表 ALTER TABLE score ADD CONSTRAINT fk_score_course FOREIGN KEY (coursename) REFERENCES course(coursename) ON UPDATE CASCADE;ON UPDATE CASCADE的意思是班級(jí)名改了學(xué)生表里對(duì)應(yīng)的班級(jí)名自動(dòng)跟著改ON DELETE SET NULL是班級(jí)刪了學(xué)生表里的班級(jí)名置空而不是刪學(xué)生ON DELETE CASCADE是學(xué)生刪了他的成績(jī)記錄也跟著刪。這三個(gè)策略要根據(jù)業(yè)務(wù)規(guī)則來(lái)選不是隨便加的。比如學(xué)生轉(zhuǎn)班只改班級(jí)名用 CASCADE 就很省事但班級(jí)撤銷(xiāo)了不能把學(xué)生也刪了所以用 SET NULL。注意加外鍵之前要確保兩張表的字段類(lèi)型完全一致。比如student.classname是VARCHAR(20)class.classname也必須是VARCHAR(20)一個(gè) CHAR 一個(gè) VARCHAR 在某些數(shù)據(jù)庫(kù)里會(huì)直接拒絕創(chuàng)建外鍵。4. 視圖、觸發(fā)器、存儲(chǔ)過(guò)程報(bào)告里最值錢(qián)的三個(gè)模塊4.1 視圖定義與查詢(xún)封裝報(bào)告里創(chuàng)建了一個(gè)student_view把學(xué)生表的所有字段包進(jìn)去。原文的寫(xiě)法是Create view student_view(...) as select * from student這個(gè)視圖本身沒(méi)什么過(guò)濾邏輯實(shí)際價(jià)值不大。但視圖的真正用途是封裝復(fù)雜查詢(xún)比如「查詢(xún)學(xué)生的學(xué)號(hào)、姓名、性別、院系、專(zhuān)業(yè)、班級(jí)信息」這個(gè)需求需要 join 學(xué)生表和班級(jí)表CREATE VIEW v_student_full AS SELECT s.sno AS 學(xué)號(hào), s.sname AS 姓名, s.ssex AS 性別, c.tdepartment AS 院系, s.sspeciality AS 專(zhuān)業(yè), s.classname AS 班級(jí) FROM student s LEFT JOIN class c ON s.classname c.classname;這個(gè)視圖的好處是前端查詢(xún)直接SELECT * FROM v_student_full WHERE 院系 計(jì)算機(jī)系就行不用每次寫(xiě) join。LEFT JOIN保證即使學(xué)生還沒(méi)分配班級(jí)學(xué)生記錄也能查出來(lái)班級(jí)字段顯示 NULL。4.2 觸發(fā)器自動(dòng)維護(hù)班級(jí)人數(shù)報(bào)告里最核心的技術(shù)點(diǎn)就是「當(dāng)新增、修改或刪除學(xué)生時(shí)自動(dòng)修改相應(yīng)班級(jí)的學(xué)生人數(shù)」。原文寫(xiě)了兩個(gè)觸發(fā)器insert_number和dele_number但語(yǔ)法問(wèn)題很多declare sno int在 MySQL 里不合法set sno(select sno from deleted)這種寫(xiě)法在 SQL Server 里才成立而且sno是 CHAR 類(lèi)型不是 INT。下面是我改寫(xiě)成 MySQL 語(yǔ)法的版本用AFTER INSERT和AFTER DELETE兩個(gè)觸發(fā)器實(shí)現(xiàn)DELIMITER $$ -- 新增學(xué)生時(shí)班級(jí)人數(shù) 1 CREATE TRIGGER trg_student_insert AFTER INSERT ON student FOR EACH ROW BEGIN UPDATE class SET number number 1 WHERE classname NEW.classname; END$$ -- 刪除學(xué)生時(shí)班級(jí)人數(shù) -1 CREATE TRIGGER trg_student_delete AFTER DELETE ON student FOR EACH ROW BEGIN UPDATE class SET number number - 1 WHERE classname OLD.classname; END$$ -- 修改學(xué)生班級(jí)時(shí)舊班級(jí) -1新班級(jí) 1 CREATE TRIGGER trg_student_update AFTER UPDATE ON student FOR EACH ROW BEGIN IF OLD.classname NEW.classname THEN UPDATE class SET number number - 1 WHERE classname OLD.classname; UPDATE class SET number number 1 WHERE classname NEW.classname; END IF; END$$ DELIMITER ;三個(gè)觸發(fā)器的邏輯說(shuō)明NEW代表插入或修改后的新行OLD代表刪除或修改前的舊行。插入時(shí)用NEW.classname找到班級(jí)加一刪除時(shí)用OLD.classname找到班級(jí)減一修改時(shí)要判斷班級(jí)是否變了變了才做一減一加沒(méi)變就不動(dòng)。DELIMITER $$是因?yàn)橛|發(fā)器體里有分號(hào)需要臨時(shí)改分隔符不然 MySQL 會(huì)在第一個(gè)分號(hào)處截?cái)唷L崾居|發(fā)器是「隱形」的邏輯調(diào)試的時(shí)候如果班級(jí)人數(shù)對(duì)不上先查student表里有沒(méi)有班級(jí)名為 NULL 的記錄再查觸發(fā)器有沒(méi)有被正確創(chuàng)建。SHOW TRIGGERS;可以列出當(dāng)前庫(kù)所有觸發(fā)器。4.3 存儲(chǔ)過(guò)程統(tǒng)計(jì)各院系男女人數(shù)報(bào)告里的存儲(chǔ)過(guò)程number用了Return(select count(...))的寫(xiě)法這個(gè)在標(biāo)準(zhǔn) SQL 里不成立RETURN只能返回一個(gè)值不能返回結(jié)果集。正確做法是用SELECT直接輸出結(jié)果或者用OUT參數(shù)。我改成 MySQL 的存儲(chǔ)過(guò)程DELIMITER $$ CREATE PROCEDURE sp_count_gender(IN p_classname VARCHAR(20)) BEGIN SELECT SUM(CASE WHEN ssex 男 THEN 1 ELSE 0 END) AS 男生人數(shù), SUM(CASE WHEN ssex 女 THEN 1 ELSE 0 END) AS 女生人數(shù), COUNT(*) AS 總?cè)藬?shù) FROM student WHERE classname p_classname; END$$ DELIMITER ;調(diào)用方式CALL sp_count_gender(計(jì)軟2101);。這個(gè)存儲(chǔ)過(guò)程用CASE WHEN做條件計(jì)數(shù)比報(bào)告里寫(xiě)兩個(gè)Return要靠譜得多。參數(shù)p_classname是輸入?yún)?shù)調(diào)用時(shí)傳入班級(jí)名即可。如果你要統(tǒng)計(jì)的是院系而不是班級(jí)把WHERE classname p_classname改成 join 班級(jí)表按tdepartment過(guò)濾就行。4.4 CHECK 約束和性別限制報(bào)告里用CONSTRAINT C4 CHECK(Ssex IN(男,女))來(lái)限制性別輸入。這個(gè)約束在 MySQL 8.0.16 之后才真正生效之前的版本會(huì)解析但忽略。如果你用的是 MySQL 5.7CHECK 約束不會(huì)報(bào)錯(cuò)但也不會(huì)攔截非法數(shù)據(jù)需要改用觸發(fā)器來(lái)實(shí)現(xiàn)-- MySQL 5.7 用觸發(fā)器替代 CHECK DELIMITER $$ CREATE TRIGGER trg_check_sex BEFORE INSERT ON student FOR EACH ROW BEGIN IF NEW.ssex NOT IN (男, 女) THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 性別只能輸入男或女; END IF; END$$ DELIMITER ;SIGNAL SQLSTATE 45000是 MySQL 里主動(dòng)拋異常的標(biāo)準(zhǔn)寫(xiě)法插入非法性別時(shí)會(huì)直接報(bào)錯(cuò)并回滾。SQL Server 用戶(hù)直接用CHECK約束就行SQL Server 對(duì) CHECK 的支持一直很完整。5. 避坑與排查這份報(bào)告里最容易翻車(chē)的五個(gè)地方5.1 觸發(fā)器報(bào)錯(cuò)「Cant update table in stored function/trigger」現(xiàn)象創(chuàng)建觸發(fā)器后插入學(xué)生記錄時(shí)報(bào)錯(cuò)提示不能在觸發(fā)器里更新被觸發(fā)的表。原因MySQL 不允許在觸發(fā)器里更新觸發(fā)器所在的那張表。比如你在student表的觸發(fā)器里寫(xiě)UPDATE student SET ...就會(huì)觸發(fā)這個(gè)錯(cuò)誤。報(bào)告原文的觸發(fā)器里沒(méi)有這個(gè)問(wèn)題但如果你自己擴(kuò)展邏輯時(shí)不小心寫(xiě)了對(duì)student表的更新就會(huì)翻車(chē)。解決把要更新的邏輯挪到另一張表上或者用存儲(chǔ)過(guò)程替代觸發(fā)器。如果確實(shí)需要更新同一張表用BEFORE觸發(fā)器配合SET NEW.字段 值的方式而不是UPDATE語(yǔ)句。5.2 班級(jí)人數(shù)越跑越多或變成負(fù)數(shù)現(xiàn)象反復(fù)插入刪除學(xué)生后class.number跟實(shí)際學(xué)生數(shù)對(duì)不上要么多了要么少了。原因三種可能。一是觸發(fā)器只建了 INSERT 沒(méi)建 DELETE刪學(xué)生時(shí)人數(shù)不減二是修改學(xué)生班級(jí)時(shí)沒(méi)有處理舊班級(jí)的減一三是直接手動(dòng)改了student表的數(shù)據(jù)但觸發(fā)器沒(méi)覆蓋到那條路徑。解決先SELECT classname, COUNT(*) FROM student GROUP BY classname;查出真實(shí)人數(shù)再UPDATE class SET number (SELECT COUNT(*) FROM student WHERE student.classname class.classname);校準(zhǔn)一次。然后檢查三個(gè)觸發(fā)器INSERT/DELETE/UPDATE是否都建了。我一般會(huì)在建完觸發(fā)器后跑一輪增刪改測(cè)試確認(rèn)人數(shù)變化符合預(yù)期再繼續(xù)。5.3 外鍵約束導(dǎo)致建表順序報(bào)錯(cuò)現(xiàn)象執(zhí)行建表腳本時(shí)score表創(chuàng)建失敗提示「Cannot add foreign key constraint」。原因外鍵引用的表還沒(méi)建。比如score表引用了student和course如果先建score再建student外鍵就找不到目標(biāo)表。另外字段類(lèi)型不匹配也會(huì)導(dǎo)致這個(gè)錯(cuò)誤比如student.sno是CHAR(15)score.sno寫(xiě)成了VARCHAR(15)。解決建表順序按依賴(lài)關(guān)系來(lái)——先建class和course再建student最后建score和jc?;蛘呦冉ㄋ斜聿患油怄I最后統(tǒng)一用ALTER TABLE追加。字段類(lèi)型用SHOW CREATE TABLE student;核對(duì)一遍確保跟外鍵定義完全一致。5.4 存儲(chǔ)過(guò)程參數(shù)傳中文亂碼現(xiàn)象調(diào)用CALL sp_count_gender(計(jì)軟2101);返回空結(jié)果但表里明明有這個(gè)班級(jí)的數(shù)據(jù)。原因數(shù)據(jù)庫(kù)、表、連接三層的字符集不一致。常見(jiàn)情況是數(shù)據(jù)庫(kù)建的時(shí)候用了latin1表用了utf8客戶(hù)端連接又用了gbk中文參數(shù)傳到存儲(chǔ)過(guò)程里就變成了亂碼匹配不上任何記錄。解決建庫(kù)時(shí)統(tǒng)一用utf8mb4連接字符串里加characterEncodingutf8。已經(jīng)建好的庫(kù)可以用ALTER DATABASE student_db CHARACTER SET utf8mb4;和ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4;修正。存儲(chǔ)過(guò)程參數(shù)定義里也要顯式寫(xiě)VARCHAR(20) CHARACTER SET utf8mb4。5.5 視圖查詢(xún)權(quán)限不足現(xiàn)象用普通用戶(hù)賬號(hào)查詢(xún)v_student_full時(shí)報(bào)錯(cuò)「SELECT command denied」。原因視圖的權(quán)限跟基表是分開(kāi)的。即使用戶(hù)對(duì)student和class表有查詢(xún)權(quán)限如果沒(méi)有單獨(dú)授予視圖的SELECT權(quán)限照樣查不了。另外如果視圖定義里用了DEFINER執(zhí)行時(shí)是以定義者的身份運(yùn)行定義者沒(méi)有權(quán)限也會(huì)失敗。解決用管理員賬號(hào)執(zhí)行GRANT SELECT ON student_db.v_student_full TO 用戶(hù)名localhost;。如果視圖用了SQL SECURITY DEFINER改成SQL SECURITY INVOKER讓視圖以調(diào)用者身份執(zhí)行權(quán)限模型會(huì)更清晰。6. 進(jìn)階驗(yàn)證用一組測(cè)試數(shù)據(jù)把整條鏈路跑通建完表、加完約束、寫(xiě)完觸發(fā)器和存儲(chǔ)過(guò)程之后別急著交報(bào)告。我一般會(huì)灌一組測(cè)試數(shù)據(jù)把增刪改查和統(tǒng)計(jì)邏輯全部走一遍確認(rèn)沒(méi)有玄學(xué)問(wèn)題。下面這套驗(yàn)證腳本你可以直接抄-- 1. 插入班級(jí) INSERT INTO class VALUES (計(jì)軟2101, 計(jì)算機(jī)系, 張老師, A101, 0); INSERT INTO class VALUES (計(jì)軟2102, 計(jì)算機(jī)系, 李老師, A102, 0); -- 2. 插入學(xué)生觸發(fā)器應(yīng)自動(dòng)把班級(jí)人數(shù)改成 2 和 1 INSERT INTO student (sno, sname, ssex, classname) VALUES (2021001, 張三, 男, 計(jì)軟2101); INSERT INTO student (sno, sname, ssex, classname) VALUES (2021002, 李四, 女, 計(jì)軟2101); INSERT INTO student (sno, sname, ssex, classname) VALUES (2021003, 王五, 男, 計(jì)軟2102); -- 3. 驗(yàn)證班級(jí)人數(shù) SELECT classname, number FROM class; -- 預(yù)期計(jì)軟2101 2計(jì)軟2102 1 -- 4. 插入課程和成績(jī) INSERT INTO course (cid, coursename) VALUES (C001, 數(shù)據(jù)庫(kù)原理); INSERT INTO score (sno, coursename, sname, score) VALUES (2021001, 數(shù)據(jù)庫(kù)原理, 張三, 88); INSERT INTO score (sno, coursename, sname, score) VALUES (2021002, 數(shù)據(jù)庫(kù)原理, 李四, 92); -- 5. 調(diào)用存儲(chǔ)過(guò)程統(tǒng)計(jì)男女人數(shù) CALL sp_count_gender(計(jì)軟2101); -- 預(yù)期男生 1 人女生 1 人總計(jì) 2 人 -- 6. 測(cè)試 CHECK 約束這條應(yīng)該報(bào)錯(cuò) INSERT INTO student (sno, sname, ssex, classname) VALUES (2021004, 趙六, 未知, 計(jì)軟2101); -- 預(yù)期報(bào)錯(cuò)性別只能輸入男或女 -- 7. 測(cè)試刪除觸發(fā)器 DELETE FROM student WHERE sno 2021003; SELECT classname, number FROM class WHERE classname 計(jì)軟2102; -- 預(yù)期計(jì)軟2102 人數(shù)變成 0 -- 8. 測(cè)試修改觸發(fā)器學(xué)生轉(zhuǎn)班 UPDATE student SET classname 計(jì)軟2102 WHERE sno 2021001; SELECT classname, number FROM class; -- 預(yù)期計(jì)軟2101 變成 1計(jì)軟2102 變成 1這套腳本跑完如果每一步的結(jié)果都跟預(yù)期一致說(shuō)明你的表結(jié)構(gòu)、約束、觸發(fā)器、存儲(chǔ)過(guò)程全部正確。如果有哪一步不對(duì)回到對(duì)應(yīng)的章節(jié)排查。最后說(shuō)一個(gè)我自己的習(xí)慣每次交課設(shè)之前我都會(huì)把整個(gè)建庫(kù)腳本刪庫(kù)重跑一遍從DROP DATABASE IF EXISTS student_db;開(kāi)始確保腳本是冪等的、不依賴(lài)任何手工操作。這個(gè)習(xí)慣幫我抓出過(guò)好幾次「本地能跑、換臺(tái)機(jī)器就報(bào)錯(cuò)」的問(wèn)題。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取