Skip to content

Latest commit

 

History

History
354 lines (262 loc) · 11.3 KB

82.11、数据定义.adoc

File metadata and controls

354 lines (262 loc) · 11.3 KB

数据定义

概述

SQLite 与 MySQL、PostgreSQL 的类型系统不太相同。后两者使用静态类型,一个列具有唯一的数据类型;而 SQLite 则使用“灵活类型”(Flexible Type),一个值的类型由值的类型确定,而非列的类型确定。

SQLite 不仅使用了灵活类型,准确来说,SQLite 不使用“数据类型(datatype)”而使用“存储类别(storage class)”来表述一个值。

SQLite 的 存储类别(storage class)

SQLite 有以下五个存储类别:

NULL

这个值是一个空(null)值

INTEGER

这个值是一个有符号整数,依照值的大小,以 0/1/2/3/4/6/8 字节存储

REAL

这个值是一个浮点数,使用 8 字节 IEEE 浮点数存储

TEXT

这个值是一个文本字符串,使用数据库的编码方式(UTF-8、UTF-16BE、UTF-16LE)存储

BLOB

这个值是一个二进制串,将以值输入的状态存储

类别规则快览

  • 在定义一个列的时候,列的存储类别是可选的。也就是说,在定义一个列的时候,可以只给出列名。

  • 若定义列时提供了存储类别,那么后者可以是任意文本。SQLite 会尝试基于后者的文本缩减值的偏好的存储类别,但这个类别是建议性的,而非强制性的。偏好存储类别被称为“列亲和类别”。

  • 当输入值时,SQLite 会尝试将数据转换为“列亲和类别”。若失败,SQLite 会以原始类型存储这个数据。

  • 常见的对应关系

    列存储类别 该列支持的类别

    INTEGER

    INTEGER, REAL, TEXT, BLOB

    REAL

    REAL, TEXT, BLOB

    TEXT

    TEXT, BLOB

    BLOB

    INTEGER, REAL, TEXT, BLOB

数字类型的字面量的类别推定

  1. 若一个数字字面量不被引号包裹、不含小数点、不含指数,则推定为 INTEGER

  2. 若一个数字字面量被引号包裹,则推定为 TEXT

  3. 若一个数字字面量不被引号包裹,但具有小数点或指数,则推定为 REAL

  4. 若一个数字字面量具有以下形式 x’ABCD' 或 x’abcd'(位数必须为 4 的整数倍),则推定为 BLOB

字面量推定的检测

字面量的类别的推定,可以用 typeof() 函数显示

SELECT
    typeof(100),
    typeof('100'),
    typeof(100.0),
    typeof(100e2),
    typeof(x'10001234'),
    typeof(NULL);

SQLite 的排序规则

  1. NULL 具有最低的等级,排在最后,NULL 之间无排序

  2. 稍高一些的是 INTEGER 和 REAL,它们将作为数值比较大小

  3. 再高一些的是 TEXT,它们将作为文本排序

  4. 最高的是 BLOB,它们通过 C 函数 memcmp() 比较

因此,在使用 ORDER BY 排序时,SQLite 会先将数据归类为四类:NULL、INTEGER/REAL、TEXT、BLOB,然后再再每类中进行排序。

日期与时间的表示方法

SQLite 中没有日期和时间特定的存储类别,SQLite 使用 TEXTREALINTEGER,并搭配特定的函数来存储/表示日期和时间值。

  • 若使用 TEXT 来表示/存储日期和时间值,
    那么字符串的格式为 ISO8601 格式的一个子集:

    // 完整形式
    YYYY-MM-DD HH:MM:SS.SSS
    
    // 仅日期
    YYY-MM-DD
    
    // 仅时间(读取时会当作 2000-01-01 当日的时间点)
    HH:MM:SS
    
    // 带小数的时间(读取时会当作 2000-01-01 当日的时间点)
    HH:MM:SS.SSS

    比如 2023-01-02 12:06:55.123

  • 若使用 REAL 来表示/存储日期时间值
    那么实际的值是自公元前 4714 年 11 月 24 日午夜记为 0 时刻的儒略历 天数(含小数)
    比如 2023-01-02 12:06:55.123 会被记为 2459947.00480466

  • 若使用 INTEGER 来表示/存储日期和时间值
    那么实际的值是自 1970-01-01 00:00:00 UTC 起的 整数秒数
    比如 2023-01-02 12:06:55.123 会被记为 1672661215(忽略了小数)

与日期时间相关的函数

SQLite 中共有 6 个与日期时间相关的函数

-- 以 YYYY-MM-DD 的形式转换时间值
data(<时间值>, <修饰符>, <修饰符>...)

-- 以 HH:MM:SS 的形式转换时间值
time(<时间值>, <修饰符>, <修饰符>...)

-- 以 YYYY-MM-DD HH:MM:SS 的形式转换时间值
datetime(<时间值>, <修饰符>, <修饰符>...)

-- 将时间值转换为儒略历计时
julianday(<时间值>, <修饰符>, <修饰符>...)

-- 将时间值转换为 UNIX 计时
unixepoch(<时间值>, <修饰符>, <修饰符>...)

-- 以指定的格式格式化时间值
strftime(<格式>, <时间值>, <修饰符>, <修饰符>...)
Important

上面函数的 <时间值> 这个参数

  • 如果参数值是 TEXT 类型的,则会当作 ISO8601 格式处理

  • 如果是 REAL 或者 INTEGER,则会当作儒略历计时来处理

若要将数值强制以 UNIX 计时处理,需要使用修饰符 unixepoch
若要将数值强制以儒略历计时处理,需要使用修饰符 julianday

Important

默认情况下,SQLite 始终使用 UTC 作为时区。

若要将 UTC 转换为本地时,需要使用修饰符 localtime
若要将本地时转换为 UTC,需要使用修饰符 utc

Note

具体格式化表和修饰符,参见 Date And Time Functions

CREATE TABLE 语句

创建一个新表

CREATE [TEMPORARY | TEMP] TABLE [IF NOT EXIST] [<数据库名>.]<表名> (
    <列名1> [<存储类别1>] [<约束>...],
    ...
    [<表约束>...]
) [WITHOUT ROWID];
  • 首先,表名不可以以 sqlite_ 开头,因为这个是 SQLite 预留的内部使用的表。

  • 其次,IF NOT EXIST 会检测是否有同名的表,若有,则不创建;
    若不指定 IF NOT EXIST,且存在同名的表,则 SQLite 会返回错误。

  • 若指定了 TEMPORARY 或者 TEMP,则表示创建的表是一个“临时的表”,
    存放在内存中,一个名为 temp 的数据库中。

  • 可以额外指定数据库名,这样这个表就可以存放在指定的数据库下。若不指定则存放在 main 数据库中。
    若指定,则数据库名必须为 maintemp 或任何一个附加的数据库的名称。

  • 之后是列的定义,一个列包含一个列名,以及可选的一个存储类型、一系列的列约束。
    列定义的结尾以逗号 , 结尾。
    SQLite 支持的列约束包括 PRIMARY KEYUNIQUENOT NULLCHECK

  • 一个表定义的最后是表约束,表约束包括 PRIMARY KEYFOREIGN KEYUNIQUECHECK

  • 最后,还有一个 WITHOUT ROWID 选项,
    默认情况下,SQLite 会为每个表添加一个隐藏的列,
    它的类型为 64 位有符号整型,用来在当前的表中唯一确定一个行。
    通常这个列被称为 rowidoidrowid
    而具有 rowid 列的表,有时候又被称为 rowid 表。
    指定了 WITHOUT ROWID 后,会关闭这个列。

除了直接创建一个表,还有一种从 SELECT 的结果创建一个表的方法

CREATE [TEMPORARY | TEMP] TABLE [IF NOT EXIST] [<数据库名>.]<表名> AS <SELECT 语句>;

CREATE TABLE 案例

我们要创建一个联系人数据库,这个联系人数据库要包含联系人的姓、名、电邮地址、电话号码,且每个电邮地址和电话号码是互不相同的。
而且,我们还需要让每个联系人属于零个或多个组,且每个组可以含有零个或多个联系人。

为了管理方便,这里我们可以创建三个表。

  • contacts 表,这个表包含的列为 联系人 id、姓、名、电邮地址、电话

  • groups 表,这个表包含的列为 组 id、组名

  • contact_group 表,这个表包含两个外键,分别是 联系人 id 和 组 id

这样我们就建立了两个独立的表,以及一个关联表。

-- 创建 contacts 表
-- 这个表记录了联系人的信息
CREATE TABLE contacts (
    -- 当一个列的存储类别为 INTEGER 且约束为 PRIMARY KEY 时,它其实是一个 rowid 列的别名
    contact_id INTEGER PRIMARY KEY,
    first_name TEXT,
    last_name TEXT,
    email TEXT UNIQUE,
    phone TEXT UNIQUE,

    -- 这里设置了一个约束,其要求 first_name 和 last_name 不能同时为空
    CHECK ((first_name IS NOT NULL) OR (last_name IS NOT NULL))

    -- 这里设置了一个约束,其要求 email 和 phone 不能同时为空
    CHECK ((email IS NOT NULL) OR (phone IS NOT NULL))
);

-- 创建 groups 表
-- 就是记录了一个组名
CREATE TABLE groups (
    group_id INTEGER PRIMARY KEY,
    -- 组名不应为空,且不应相同
    name TEXT NOT NULL UNIQUE
);

-- 最后是这个关联表
-- 由于 联系人和群组是 M:N 的对应关系
-- 因此不好直接使用包含关系
-- 另列一张表会比较合适
CREATE TABLE contact_group (
    link_id INTEGER PRIMARY KEY,
    contact_id INTEGER NOT NULL,
    group_id INTEGER NOT NULL,

    -- 这里要求 contact_id 和 group_id 的组合不可以相同
    -- 保证了不会记录多个相同的关联
    UNIQUE (contact_id, group_id),

    -- 设置了两个外键

    -- 将本表中的 contact_id 设置为外键
    FOREIGN KEY (contact_id)
        -- 其参考的外键为 contacts 表的 contact_id 这个列
        REFERENCES contacts (contact_id)
            -- 若主表的这个值被删除时,删除子表中所有相关的条目
            ON DELETE CASCADE
            -- 若主表的这个值被更新时,禁止更新主表的这个值(除非本表中所有相关的条目均被移除)
            ON UPDATE NO ACTION,

    FOREIGN KEY (group_id)
        REFERENCES groups (group_id)
            ON DELETE CASCADE
            ON UPDATE NO ACTION
);

让我们尝试向其中插入一些数据

INSERT INTO
    contacts (first_name, last_name, email, phone)
VALUES
    ('Alice', 'Joe', 'alice@example.com', NULL),
    ('Bob', 'Turing', NULL, '123-456789');

INSERT INTO
    groups (name)
VALUES
    ('family'),
    ('colleague');

INSERT INTO
    contact_group (contact_id, group_id)
VALUES
    (
        (SELECT contact_id FROM contacts WHERE last_name IS 'Joe'),
        (SELECT group_id FROM groups WHERE name IS 'family')
    ),
    (
        (SELECT contact_id FROM contacts WHERE first_name IS 'Bob'),
        (SELECT group_id FROM groups WHERE name IS 'colleague')
    );

ALTER TABLE 语句

SQLite 支持的 ALTER TABLE 语句的功能仅为 SQL 标准中的一小部分。

SQLite 的 ALTER TABLE 主要负责

  • 表名的修改

  • 列的添加

  • 列的删除

  • 列的改名

ALTER TABLE <表名>
    RENAME TO <新表名>;

ALTER TABLE <表名>
    ADD [COLUMN] <列定义语句>;

ALTER TABLE <表名>
    RENAME [COLUMN] <列名> TO <新列名>;

ALTER TABLE <表名>
    DROP [COLUMN] <列名>;

VACUUM 语句

清理硬盘上的数据库文件

即便执行了 DROP / DELETE 操作,SQLite 也不会自动释放磁盘空间,被 DROP 的磁盘空间会用于新数据的存储,
若确实需要释放磁盘空间(且整理数据库碎片),则需要使用 VACUUM 语句。

VACCUM [<数据库名> INTO '<新的数据库文件路径>'];

VACCUM 的默认操作流程为:

  1. 在硬盘上备份整个数据库

  2. 对新生成的数据库执行清理操作

  3. 替换旧的数据库

由于有备份的操作,因此硬盘空间不够的话,会导致 VACUUM 失败。

另外可以使用 VACUUM INTO 将新的数据库写入到另一个文件中