4. 表的创建与管理:从零开始搭积木
数据库编程,说白了就是跟表打交道。表是数据的容器,就像Excel里的工作表。但数据库的表更讲究规矩——你得先告诉它:你要存什么类型的数据,哪些数据不能为空,哪一列是唯一标识。今天我们就聊聊怎么用C语言和SQLite来创建、修改和删除表。
4.1 CREATE TABLE:先画好格子再填数据
创建表之前,我习惯先画个草图。比如我要存用户信息,脑子里先过一遍:用户名、邮箱、年龄、注册时间……嗯,差不多了。然后才是写SQL语句。
在C语言里,我们通过sqlite3_exec()来执行建表语句。看个例子:
#include <sqlite3.h>
#include <stdio.h>
int main() {
sqlite3 *db;
char *err_msg = 0;
// 打开数据库
sqlite3_open("test.db", &db);
// 建表SQL
const char *sql =
"CREATE TABLE IF NOT EXISTS users ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" name TEXT NOT NULL,"
" email TEXT UNIQUE NOT NULL,"
" age INTEGER DEFAULT 18,"
" created_at TEXT DEFAULT (datetime('now'))"
");";
// 执行
if (sqlite3_exec(db, sql, 0, 0, &err_msg) != SQLITE_OK) {
fprintf(stderr, "建表失败: %s\n", err_msg);
sqlite3_free(err_msg);
} else {
printf("表创建成功!\n");
}
sqlite3_close(db);
return 0;
}
注意那个IF NOT EXISTS——我在项目中吃过亏,重复执行建表语句会报错。加上这个,安全多了。
4.2 数据类型:别把数字当字符串存
SQLite的数据类型比较灵活,但灵活不等于随便。我个人建议:
| 类型 | 说明 | 我常用的场景 |
|---|---|---|
| INTEGER | 整型,占1-8字节 | ID、年龄、计数 |
| REAL | 浮点型,8字节 | 价格、评分 |
| TEXT | 字符串,UTF-8编码 | 名字、描述、邮箱 |
| BLOB | 二进制数据 | 图片、文件(不常用) |
有一次我接手一个项目,发现年龄字段用的是TEXT。你想想看,存"25"和25,排序时结果完全不一样。字符串排序是"100"排在"20"前面,因为按字符比。嗯,这就是坑。
4.3 约束条件:给数据上规矩
约束条件就是规则。没有规则的表,数据会乱成一锅粥。
PRIMARY KEY:每行数据的身份证
主键必须唯一,且不能为空。我习惯用自增整数做主键:
id INTEGER PRIMARY KEY AUTOINCREMENT
为什么用AUTOINCREMENT?因为SQLite默认会复用被删除的ID。比如你删了第3条记录,下一条新记录的ID可能是3。如果你不希望这样,就用AUTOINCREMENT保证ID只增不减。
NOT NULL:别给我空值
有些字段必须有值。比如用户名,你总不能注册一个没有名字的用户吧?
name TEXT NOT NULL
我曾经在日志系统里忘了加NOT NULL,结果有一批日志的"操作人"字段全是空的。排查了半天,才发现是代码里漏了赋值。从那以后,凡是业务上必须有的字段,我都加NOT NULL。
UNIQUE:重复?不行
邮箱、手机号、身份证号——这些天然就是唯一的。用UNIQUE约束,数据库帮你把关:
email TEXT UNIQUE NOT NULL
注意:UNIQUE允许NULL值存在多个。如果你想让NULL也唯一,得用其他手段。
核心要点:约束条件不是摆设,它们是数据质量的最后一道防线。别指望应用程序能100%过滤脏数据。
4.4 表的删除:DROP TABLE
删表很简单,但后果很严重。看代码:
const char *sql = "DROP TABLE IF EXISTS users;";
sqlite3_exec(db, sql, 0, 0, &err_msg);
加上IF EXISTS,表不存在时不会报错。但注意——删表会连数据一起删掉,没有回收站。
警告:生产环境执行DROP TABLE前,务必确认三件事:1) 有没有备份?2) 有没有其他表依赖它?3) 当前有没有人在用?
4.5 表的修改:ALTER TABLE
SQLite对ALTER TABLE的支持有限。你只能做两件事:
- 重命名表:
ALTER TABLE old_name RENAME TO new_name; - 添加列:
ALTER TABLE users ADD COLUMN phone TEXT;
不能删除列,不能修改列的类型,不能添加约束。为什么?SQLite的设计哲学是轻量,这些复杂操作可以通过重建表来实现。
我遇到过一个需求:要给已有表加一个唯一约束。SQLite不支持直接加,怎么办?我的做法是:
- 创建新表,带上约束
- 把旧数据插入新表
- 删旧表,重命名新表
代码大概这样:
// 1. 创建新表
sqlite3_exec(db, "CREATE TABLE users_new (...);", 0, 0, 0);
// 2. 复制数据
sqlite3_exec(db, "INSERT INTO users_new SELECT * FROM users;", 0, 0, 0);
// 3. 切换表
sqlite3_exec(db, "DROP TABLE users;", 0, 0, 0);
sqlite3_exec(db, "ALTER TABLE users_new RENAME TO users;", 0, 0, 0);
注意:这期间要加锁,防止数据写入。我在线上做过一次,凌晨两点,用户最少的时候。
4.6 知识体系总览
下面这张图,是我自己总结的表操作核心流程:
4.7 避坑指南
我的经验:
- 建表时就把约束想全,后期改表很麻烦
- 测试环境用
DROP TABLE IF EXISTS,生产环境慎用 - 字段名别用SQLite保留字,比如
group、order——我踩过这个坑 - 字符串长度不用指定,SQLite的TEXT没有长度限制
好了,表的基本操作就这些。记住:建表是数据库编程的第一步,也是最重要的一步。表结构设计得好,后面写查询、做统计都顺手。设计得不好……嗯,你会花很多时间在数据清洗上。
公众号:蓝海资料掘金营,微信deep3321