import pymysqlconn = pymysql.connect(host='localhost', port=6033, user='root', password='1qaz@wsx', charset='utf8mb4', database='pythondb') # 連結資料庫with conn.cursor() as cursor: sql = """ CREATE TABLE IF NOT EXISTS scores ( ID int NOT NULL AUTO_INCREMENT PRIMARY KEY, Name varchar(20), Chinese int(3), English int(3), Math int(3) ); """ cursor.execute(sql) # 執行 SQL 指令 conn.commit() # 提交資料庫conn.close()
import pymysqlconn = pymysql.connect(host='localhost', port=6033, user='root', password='1qaz@wsx', charset='utf8mb4', database='pythondb') # 連結資料庫with conn.cursor() as cursor: sql = "update scores set Chinese = 98 where ID = 4" cursor.execute(sql) conn.commit() sql = "select * from scores where ID = 4" cursor.execute(sql) data = cursor.fetchone() print(data)
執行結果
(4, '劉大樹', 98, 87, 89)
⚠️ where 忘記寫的後果
update scores set Chinese = 98 沒有 where → 整張表每一筆的國文都會變成 98。 delete from scores 沒有 where → 整張表資料全部清空。
養成習慣:先用 select 加同樣的 where 確認範圍,再改成 update / delete。
5-4 刪除資料 (DELETE)
SQL 語法
delete from 資料表 where 條件式
範例:mysqldelete.py —— 刪除座號 4 號同學的資料
import pymysqlconn = pymysql.connect(host='localhost', port=6033, user='root', password='1qaz@wsx', charset='utf8mb4', database='pythondb') # 連結資料庫with conn.cursor() as cursor: sql = "delete from scores where ID = 4" cursor.execute(sql) conn.commit() sql = "select * from scores" cursor.execute(sql) data = cursor.fetchall() print(data)
import pymysqlconn = pymysql.connect( host='localhost', port=6033, user='root', password='1qaz@wsx', charset='utf8mb4', database='pythondb')try: with conn.cursor() as cursor: # 建立資料表 cursor.execute(""" CREATE TABLE IF NOT EXISTS scores ( ID int NOT NULL AUTO_INCREMENT PRIMARY KEY, Name varchar(20), Chinese int, English int, Math int ) """) # 新增資料 cursor.execute(""" insert into scores (Name, Chinese, English, Math) values ('李大毛',95,92,80), ('林小明',82,83,61), ('黃小英',74,53,71) """) conn.commit() # 查詢資料 cursor.execute("select * from scores") for row in cursor.fetchall(): print(row)finally: conn.close() # 不論有沒有出錯都關閉連線
為什麼包 try / finally
中間任何一行出錯,conn.close() 仍會執行,不會留下沒關掉的連線。
七、常見錯誤排查
錯誤訊息
原因
解法
ModuleNotFoundError: No module named 'pymysql'
沒安裝模組
pip install pymysql
Can't connect to MySQL server on 'localhost'
MySQL 服務沒啟動 / 埠位不對
到 Uniform Server 按 Start MySQL;確認埠位 6033
Access denied for user 'root'@'localhost'
帳號或密碼錯誤
確認密碼(Uniform Server 預設 1qaz@wsx)
Unknown database 'pythondb'
資料庫還沒建立
先到 phpMyAdmin 建立 pythondb
Table 'pythondb.scores' doesn't exist
資料表還沒建立
先執行建立資料表的程式
資料查得到,但重開程式就不見了
忘記 conn.commit()
新增/更新/刪除後一定要 commit
中文變成 ??? 或亂碼
編碼不符
連線與資料庫都用 utf8mb4
DeprecationWarning: 'db' is deprecated
用了舊參數名
改用 database= / password=
第二部分會遇到的錯誤(詳見九~十五)
錯誤訊息
原因
解法
Out of range value for column 'cID'
整數型態容量不足(TINYINT 上限 255)
ALTER TABLE ... MODIFY COLUMN cID SMALLINT UNSIGNED ...
# ❌ 絕對不要這樣寫name = input("請輸入姓名:")sql = "select * from students where cName = '" + name + "'"cursor.execute(sql)
如果使用者輸入的是:
' OR '1'='1
拼出來的 SQL 會變成:
select * from students where cName = '' OR '1'='1'
'1'='1' 永遠成立 → 整張表的資料全部被撈出來。
更糟的輸入還能做到刪表:
'; DROP TABLE students; --
正確作法:用 %s 佔位符
把值交給驅動去處理,不要自己拼字串。
# ✅ 正確name = input("請輸入姓名:")cursor.execute("select * from students where cName = %s", (name,))
驅動會自動幫值加上引號並跳脫特殊字元,剛才的惡意輸入會被安全地當成「一個普通的字串」處理:
select * from students where cName = '\' OR \'1\'=\'1'
查不到這個姓名 → 回傳空結果,攻擊失效。
四大指令的參數化寫法
# 新增cursor.execute( "INSERT INTO students (cName, cSex, cBirthday, cEmail, cPhone, cAddr) " "VALUES (%s, %s, %s, %s, %s, %s)", ('王小明', 'M', '1995-08-20', 'ming@example.com', '0912345678', '台北市信義路100號'))# 查詢cursor.execute("SELECT * FROM students WHERE cSex = %s AND cHeight > %s", ('M', 175))# 更新cursor.execute("UPDATE students SET cEmail = %s WHERE cID = %s", ('new@mail.com', 5))# 刪除cursor.execute("DELETE FROM students WHERE cID = %s", (5,))
⚠️ 三個新手最常犯的錯
1. %s 不要自己加引號
cursor.execute("... where cName = '%s'", (name,)) # ❌ 多了單引號cursor.execute("... where cName = %s", (name,)) # ✅
驅動會自己處理引號,自己加反而會壞掉。
2. 只有一個參數時,別忘了逗號
cursor.execute("... where cID = %s", (5)) # ❌ (5) 是整數不是 tuplecursor.execute("... where cID = %s", (5,)) # ✅ 有逗號才是 tuple
3. 不要用 f-string 或 % 拼 SQL
cursor.execute(f"select * from students where cName = '{name}'") # ❌ 同樣有漏洞cursor.execute("select * from students where cName = '%s'" % name) # ❌ 同樣有漏洞
import mysql.connectorconn = Nonetry: conn = mysql.connector.connect(**DB_CONFIG) with conn.cursor() as cursor: cursor.execute("INSERT INTO students (cName, cSex) VALUES (%s, %s)", ('王小明', 'M')) conn.commit()except mysql.connector.Error as e: if conn: conn.rollback() print(f"資料庫錯誤:{e}")finally: if conn: conn.close()
十三、資料表設計進階
第一部分的 scores 只有 5 個欄位、型態單純。實務上的資料表會複雜得多 —— 以 students 為例:
CREATE TABLE IF NOT EXISTS students ( cID SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, cName VARCHAR(20) NOT NULL, cSex ENUM('F','M') NOT NULL DEFAULT 'F', cBirthday DATE NOT NULL, cEmail VARCHAR(100) DEFAULT NULL, cPhone VARCHAR(50) DEFAULT NULL, cAddr VARCHAR(255) DEFAULT NULL, cHeight TINYINT UNSIGNED DEFAULT NULL, cWeight TINYINT UNSIGNED DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
def load_existing_data(): """讀出現有資料,回傳三個集合供比對""" conn = mysql.connector.connect(**DB_CONFIG) with conn.cursor() as cursor: cursor.execute("SELECT cName, cEmail, cPhone FROM students") rows = cursor.fetchall() conn.close() return { "names": {r[0] for r in rows}, "emails": {r[1].lower() for r in rows if r[1]}, # 統一小寫再比對 "phones": {r[2] for r in rows if r[2]}, }
為什麼用 set 不用 list
list
set
判斷 x in ...
逐一比對,O(n)
雜湊查找,O(1)
1000 筆資料查 1000 次
約 100 萬次比對
約 1000 次
這是教「為什麼要選對資料結構」的絕佳例子 —— 同樣的邏輯,換個型態就快幾百倍。
技巧 2:產生時即時同步集合
used_names = existing["names"].copy()for _ in range(count): for attempt in range(1000): # 重試上限,避免無窮迴圈 name = generate_chinese_name(sex) if name not in used_names: used_names.add(name) # ⭐ 立刻加入,避免新資料之間重複 break else: print("素材組合已用盡,提前停止") break
from faker import Fakerfake = Faker("zh_TW") # ⭐ 指定台灣本地化
常用方法(實測 Faker 40.19.1 / zh_TW)
方法
產生內容
實際輸出範例
fake.name_male()
男性中文姓名
陳金龍
fake.name_female()
女性中文姓名
莊慧玲
fake.address()
台灣地址
200 竹北龍山寺路7段8號之0
fake.user_name()
英文帳號
hsiu-chenlin
fake.numerify("######")
依樣板填入隨機數字
235116
fake.date_between(start_date=…, end_date=…)
區間內隨機日期
1985-08-23
兩種作法對照
手刻素材(generate_students.py)
Faker(generate_students_faker.py)
相依套件
無(只用 random)
需 pip install faker
程式長度
長(素材常數佔一大半)
短很多
資料真實度
素材有限,會有重複感
內建大量真實素材
可控性
完全自訂(要哪些姓氏就哪些)
依套件提供的為準
教學價值
看得到組合邏輯與機率
學會善用現成輪子
兩份都留著,教學價值不同
先教手刻版 —— 學生才會理解「隨機資料是怎麼被組出來的」、為什麼會有組合上限
再教 Faker 版 —— 對照之下體會「不要重造輪子」,同時學會查套件文件
這是很自然的一課:先知其所以然,再學會偷懶。
⭐ Faker.seed() 讓結果可重現
Faker.seed(42)random.seed(42)
設定種子後,每次執行產生的資料完全相同(實測驗證):
Faker.seed(42); print([fake.name_male() for _ in range(3)])# ['陳金龍', '莊文章', '陳俊雄']Faker.seed(42); print([fake.name_male() for _ in range(3)])# ['陳金龍', '莊文章', '陳俊雄'] ← 完全一樣