如何在 SQLite 中创建 CHECK 约束

本文介绍如何在 SQLite 中创建 CHECK 约束,以确保数据的完整性和一致性。

发布于

数据完整性是数据库设计的核心要素,而 CHECK 约束就是维护数据质量的守门人。想象一下,如果没有规则限制,数据库就可能存入员工年龄为负数或订单日期来自未来的荒谬数据。SQLite 的 CHECK 约束功能让我们能够为数据设定合理的业务规则,从源头上杜绝这些异常情况的发生。

为什么需要 CHECK 约束

CHECK 约束就像数据的智能过滤器,它在数据写入前自动验证信息的有效性。与应用程序层面的验证相比,数据库层面的 CHECK 约束具有三大优势:

  • 可靠性:即使应用程序存在漏洞,数据库依然能坚守数据规则
  • 一致性:所有数据修改操作(插入、更新)都遵循相同规则
  • 高效性:验证过程在数据库引擎内部完成,性能更高

创建表时定义 CHECK 约束

最基本的用法是在建表语句中直接嵌入约束条件。

单列约束示例

CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    age INTEGER CHECK (age >= 18 AND age <= 65),
    salary REAL CHECK (salary > 0)
);

这里我们确保员工年龄在 18-65 岁之间,且工资必须为正数。

多列联合约束

CREATE TABLE reservations (
    id INTEGER PRIMARY KEY,
    room_number INTEGER,
    check_in_date TEXT,
    check_out_date TEXT,
    CHECK (check_out_date > check_in_date)
);

这个约束保证退房日期必须晚于入住日期。

修改表添加 CHECK 约束

对于已存在的表,我们可以通过修改表结构来增加约束条件。

添加新列时附带约束

ALTER TABLE products
ADD COLUMN discount_price REAL
CHECK (discount_price < regular_price);

为现有列添加约束

-- SQLite 需要重建表来实现这个操作
CREATE TABLE new_users (
    id INTEGER PRIMARY KEY,
    username TEXT NOT NULL,
    password TEXT CHECK (length(password) >= 8)
);

INSERT INTO new_users
SELECT * FROM users;

DROP TABLE users;
ALTER TABLE new_users RENAME TO users;

复杂业务规则实现

CHECK 约束可以表达相当复杂的业务逻辑,远超简单的范围检查。

正则表达式验证

CREATE TABLE customers (
    id INTEGER PRIMARY KEY,
    email TEXT CHECK (email LIKE '%_@__%.__%')
);

虽然 SQLite 不支持完整正则,但可以用 LIKE 实现基础格式检查。

枚举值约束

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    status TEXT CHECK (status IN ('pending', 'processing', 'shipped', 'delivered'))
);

条件组合约束

CREATE TABLE bank_accounts (
    id INTEGER PRIMARY KEY,
    account_type TEXT,
    balance REAL,
    CHECK (
        (account_type = 'savings' AND balance >= 0) OR
        (account_type = 'credit' AND balance <= 10000)
    )
);

约束命名与错误处理

为约束命名可以让我们更清晰地处理违反约束的情况。

命名约束示例

CREATE TABLE students (
    id INTEGER PRIMARY KEY,
    score INTEGER,
    CONSTRAINT score_range CHECK (score BETWEEN 0 AND 100)
);

获取约束错误信息

当约束被违反时,SQLite 会返回类似这样的错误:

CHECK constraint failed: score_range

命名的约束能让我们快速定位问题所在。

实际应用案例

让我们看几个 CHECK 约束在实际业务中的典型应用。

电商库存管理

CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price REAL CHECK (price > 0),
    stock INTEGER CHECK (stock >= 0),
    weight REAL CHECK (weight > 0 AND weight < 1000),
    CHECK (NOT (stock > 0 AND discontinued = 1))
);

医院预约系统

CREATE TABLE appointments (
    id INTEGER PRIMARY KEY,
    patient_id INTEGER NOT NULL,
    doctor_id INTEGER NOT NULL,
    appointment_time TEXT CHECK (
        strftime('%H:%M', appointment_time) BETWEEN '08:00' AND '18:00' AND
        strftime('%w', appointment_time) NOT IN ('0', '6')
    ),
    duration INTEGER CHECK (duration IN (15, 30, 45, 60)),
    CHECK (doctor_id != patient_id)
);

智能家居设备日志

CREATE TABLE device_logs (
    id INTEGER PRIMARY KEY,
    device_id INTEGER NOT NULL,
    event_time TEXT CHECK (event_time <= datetime('now')),
    temperature REAL CHECK (temperature BETWEEN -40 AND 100),
    power_consumption REAL CHECK (power_consumption >= 0),
    event_type TEXT CHECK (event_type IN ('on', 'off', 'alert', 'normal'))
);

总结

SQLite 的 CHECK 约束是维护数据完整性的强大工具:

  • 基础检查:确保数值在合理范围内,日期顺序正确
  • 复杂规则:实现多列关联验证和业务逻辑约束
  • 灵活应用:支持正则模式、枚举值和条件组合
  • 错误预防:在数据写入前拦截无效信息

在实际开发中,建议将最基本的业务规则通过 CHECK 约束实现,这样即使应用程序逻辑变更或出现漏洞,核心数据质量仍能得到保障。同时要注意,过于复杂的约束可能影响性能,对于特别复杂的业务规则,可能需要考虑在应用层实现部分验证逻辑。