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. 创建新表,带上约束
  2. 把旧数据插入新表
  3. 删旧表,重命名新表

代码大概这样:

// 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 知识体系总览

下面这张图,是我自己总结的表操作核心流程:

表操作核心流程 CREATE TABLE 定义结构 + 约束 数据类型 INTEGER/TEXT/REAL 约束条件 PRIMARY KEY / NOT NULL / UNIQUE 正常使用 ALTER TABLE DROP TABLE 注意:ALTER TABLE 只能重命名和加列 复杂修改需重建表

4.7 避坑指南

我的经验:

  • 建表时就把约束想全,后期改表很麻烦
  • 测试环境用DROP TABLE IF EXISTS,生产环境慎用
  • 字段名别用SQLite保留字,比如grouporder——我踩过这个坑
  • 字符串长度不用指定,SQLite的TEXT没有长度限制

好了,表的基本操作就这些。记住:建表是数据库编程的第一步,也是最重要的一步。表结构设计得好,后面写查询、做统计都顺手。设计得不好……嗯,你会花很多时间在数据清洗上。

公众号:蓝海资料掘金营,微信deep3321