### **SQLite 的五大类型亲和性**

首先，我们来创建一个能容纳所有“居民”的“多功能公寓楼”，也就是一张包含所有核心数据类型的表。

#### 【SQL/Shell实战】

```sql
-- 创建一个名为 "diverse_data_vault" 的表
-- 这张表将作为我们探索所有数据类型的实验平台
CREATE TABLE diverse_data_vault (
    -- INTEGER Affinity: 通常用于存储整数，如 ID、计数器等
    item_id         INTEGER PRIMARY KEY,

    -- TEXT Affinity: 用于存储字符串，如产品名称、描述
    item_name       TEXT NOT NULL,

    -- REAL Affinity: 用于存储浮点数，如价格、测量值
    price           REAL,

    -- NUMERIC Affinity: 灵活性最高的类型，能适应多种数据
    serial_number   NUMERIC,

    -- BLOB Affinity: 用于存储二进制数据，如图片、文件
    raw_data        BLOB
);
```

现在，让我们逐一拜访这五种核心的类型“居民”。

-----

### 1\. INTEGER (整数亲和性)

* **【核心概念】:** 用于存储整数（不带小数点的数字），无论是大是小，正数还是负数。

* **【生活类比】:** 把它想象成一个“鸡蛋盒”。你只能放完整的鸡蛋（整数），比如1个、2个或12个。你绝对放不进去“半个”鸡蛋（小数），如果你硬要放一个写着“6”的纸条，SQLite 会很智能地帮你换成6个真鸡蛋。

* **【SQL/Shell实战】:**

  ```sql
  -- 写入数据：注意，我们故意写入了一个文本'2'和一个浮点数3.9
  INSERT INTO diverse_data_vault (item_id, item_name) VALUES (1, 'Apple');
  INSERT INTO diverse_data_vault (item_id, item_name) VALUES ('2', 'Banana'); -- '2' 会被强制转换为整数 2
  INSERT INTO diverse_data_vault (item_id, item_name) VALUES (3.9, 'Cherry'); -- 3.9 会被截断为整数 3

  -- 查询存储的真实类型
  SELECT item_id, typeof(item_id) FROM diverse_data_vault;
  -- 输出:
  -- 1|integer
  -- 2|integer
  -- 3|integer
  ```

* **【原理解析】:** SQLite 的设计哲学之一是“尽可能做对的事，不要无谓地报错”。当你向一个具有 INTEGER 亲和性的列插入数据时，SQLite会首先尝试将该值转换为整数。如果可以无损转换（如文本'123'），它就存为整数。如果转换会有损失（如3.9转为3），它也依然会转换。这种行为在处理来自不同源头的杂乱数据时非常宽容和有效。

* **【关键要点/避坑指南】:**

    * **常见声明:** `INT`, `INTEGER`, `TINYINT`, `SMALLINT`, `BIGINT`, `BOOLEAN`, `DATE`, `DATETIME` 都会赋予列 INTEGER 亲和性。
    * **自动优化:** SQLite 会根据数值的大小，自动选择1, 2, 3, 4, 6, 或 8 字节来存储，非常节省空间。
    * **避坑:** 小数部分会被直接截断，不是四舍五入！`3.9` 存进去会变成 `3`。

* **【思考时间】:** 如果你将一个无法转换为数字的文本（比如 'hello'）插入到一个 `INTEGER` 类型的列中，你觉得会发生什么？是会报错，还是会以某种形式存进去？（提示：可以自己动手试一下！）

-----

### 2\. TEXT (文本亲和性)

* **【核心概念】:** 用于存储任何文本字符串，编码默认为 UTF-8。

* **【生活类比】:** 这就像一个“万能记事本”。你可以在上面写下名字、一首诗、一串数字或者JSON。它忠实地记录你写下的任何字符，不会做任何改变。

* **【SQL/Shell实战】:**

  ```sql
  -- 写入数据：我们将数字和NULL值写入TEXT列
  UPDATE diverse_data_vault SET item_name = 12345 WHERE item_id = 1;
  UPDATE diverse_data_vault SET item_name = NULL WHERE item_id = 2;

  -- 查询存储的真实类型
  SELECT item_id, item_name, typeof(item_name) FROM diverse_data_vault WHERE item_id in (1, 2);
  -- 输出:
  -- 1|12345|text
  -- 2||null
  ```

* **【原理解析】:** “为什么设计成这样？” 因为在动态语言和数据交换格式（如JSON）横行的世界里，数据的形态非常灵活。将数字`123`存为文本`'123'`可以保留其原始形态，避免了不必要的转换和精度损失。SQLite 认为数据的原始形态是有价值的。

* **【关键要点/避坑指南】:**

    * **常见声明:** `TEXT`, `VARCHAR(N)`, `CHAR(N)`, `CLOB` 等包含 `CHAR` 的类型都会赋予列 TEXT 亲和性。
    * **长度无效:** `VARCHAR(20)` 中的 `20` 在 SQLite 中并**不**限制字符串的长度，它仅仅是用来决定亲和性的一个标志。你可以存入任意长度的文本。
    * **避坑:** 当你对一个存储了数字文本的 TEXT 列进行排序时，它会按照字典顺序排（'100' \< '20'），而不是数值大小！

* **【思考时间】:** 如果一个 TEXT 列存储了日期字符串，比如 '2025-09-15' 和 '2025-10-01'，直接用 `ORDER BY` 对该列排序，结果会正确吗？为什么？

-----

### 3\. REAL (浮点数亲和性)

* **【核心概念】:** 用于存储8字节的浮点数，即带有小数的数字。

* **【生活类比】:** 这好比一个“温度计”。它可以显示精确到小数点的温度，比如 25.5℃。它为那些需要小数精度的数据提供了住所。

* **【SQL/Shell实战】:**

  ```sql
  -- 写入数据：整数、文本形式的浮点数都会被转换
  UPDATE diverse_data_vault SET price = 99 WHERE item_id = 1;      -- 整数 99 存为 99.0
  UPDATE diverse_data_vault SET price = '3.14159' WHERE item_id = 2; -- 文本 '3.14159' 存为 3.14159
  UPDATE diverse_data_vault SET price = 'not a number' WHERE item_id = 3; -- 无法转换，存为文本

  -- 查询存储的真实类型
  SELECT item_id, price, typeof(price) FROM diverse_data_vault;
  -- 输出:
  -- 1|99.0|real
  -- 2|3.14159|real
  -- 3|not a number|text
  ```

* **【原理解析】:** 与 INTEGER 类似，SQLite 会尽力将存入的值转换为 REAL。它遵循 IEEE 754 标准，这是几乎所有现代编程语言和处理器用于浮点数计算的标准，保证了跨平台的兼容性和性能。

* **【关键要点/避坑指南】:**

    * **常见声明:** `REAL`, `FLOAT`, `DOUBLE` 会赋予列 REAL 亲和性。
    * **精度问题:** 和所有浮点数一样，REAL 类型可能存在微小的精度误差。不应该用于需要精确计算的金融领域（比如存储货币）。
    * **避坑:** 比较两个 REAL 值是否“相等”时要格外小心。`WHERE price = 0.3` 可能会因为内部表示的微小差异而失败。通常建议使用范围比较：`WHERE price > 0.29 AND price < 0.31`。

* **【思考时间】:** 如果你把一个非常非常大的整数（超过了64位整数的表示范围）存入 REAL 类型的列，会发生什么？数据会丢失吗？

-----

### 4\. BLOB (二进制亲和性)

* **【核心概念】:** 用于存储“二进制大对象”(Binary Large Object)，也就是任何原始的字节序列，SQLite 不会对其做任何解析或转换。

* **【生活类比】:** BLOB 就像一个“保险箱”。你把一张照片、一段录音或者一个加密文件放进去，它就原封不动地替你保管。它不关心里面是什么，只是忠实地存储和提取这些原始数据。

* **【SQL/Shell实战】:**

  ```sql
  -- 写入数据：使用十六进制字面量 X'...' 来表示二进制数据
  -- 假设 X'4A504547...' 是一张JPEG图片的开头部分
  UPDATE diverse_data_vault SET raw_data = X'4A504547FFD8' WHERE item_id = 1;
  UPDATE diverse_data_vault SET raw_data = 'just text' WHERE item_id = 2; -- 文本也会原样存入

  -- 查询存储的真实类型
  SELECT item_id, typeof(raw_data) FROM diverse_data_vault;
  -- 输出:
  -- 1|blob
  -- 2|blob
  ```

* **【原理解析】:** BLOB 的设计目标是“所见即所得”。它保证了数据的完整性，一个字节都不会多，一个字节都不会少。这使得 SQLite 可以被用作各种应用的文件容器，比如存储缩略图、配置文件、甚至是小型音视频片段。

* **【关键要点/避坑指南】:**

    * **常见声明:** 只有 `BLOB` 关键字本身会赋予列 BLOB 亲和性。如果未指定类型，也默认为 BLOB。
    * **无转换:** 存入 BLOB 列的数据**永远不会**被转换类型。你存入文本 '123'，它就存储这3个字节的ASCII码，而不是整数123。
    * **避坑:** 虽然方便，但在数据库中存储非常大的文件（比如几十兆的视频）可能会导致数据库文件急剧膨胀，影响备份效率和查询性能。通常建议存储文件的路径，而不是文件本身。

* **【思考时间】:** 在 BLOB 列和 TEXT 列中都存入字符串 'hello'，它们在数据库文件中存储的内容会有任何区别吗？

-----

### 5\. NUMERIC (数字亲和性)

* **【核心概念】:** 最“聪明”也最“随和”的一种亲和性，它会根据存入值的格式，自动选择以 INTEGER, REAL, 或 TEXT 格式来存储。

* **【生活类比】:** NUMERIC 好比一个“变形机器人玩具”。你给它一个整数，它就变成一个数字机器人（INTEGER）；你给它一个小数，它就变成一架能飞的飞机（REAL）；如果你给它一个它看不懂的字符串，它就干脆变成那个字符串本身（TEXT），绝不丢失任何信息。

* **【图解原理】:**

  ```mermaid
  flowchart TD
      A[输入一个值到NUMERIC列] --> B{值看起来像整数吗?};
      B -- 是 --> C[存为INTEGER];
      B -- 否 --> D{值看起来像浮点数吗?};
      D -- 是 --> E[存为REAL];
      D -- 否 --> F[存为TEXT];
  ```

* **【SQL/Shell实战】:**

  ```sql
  -- 写入各种类型的数据
  UPDATE diverse_data_vault SET serial_number = 1234567890 WHERE item_id = 1;       -- 整数
  UPDATE diverse_data_vault SET serial_number = '987.65' WHERE item_id = 2;         -- 文本形式的浮点数
  UPDATE diverse_data_vault SET serial_number = 'SN-00003' WHERE item_id = 3;       -- 无法转换为数字的文本

  -- 查询存储的真实类型，见证奇迹！
  SELECT item_id, serial_number, typeof(serial_number) FROM diverse_data_vault;
  -- 输出:
  -- 1|1234567890|integer
  -- 2|987.65|real
  -- 3|SN-00003|text
  ```

* **【原理解析】:** NUMERIC 亲和性是 SQLite "灵活"设计哲学的终极体现。它的核心原则是：**如果一个值可以被无损地转换为数字，那就转；如果转换会丢失信息（比如 '3.1400' 转为 REAL 会丢掉末尾的0），那就保持其文本形态。** 这在处理那些格式不统一，但又希望尽可能进行数学运算的列时，提供了巨大的便利。

* **【关键要点/避坑指南】:**

    * **常见声明:** `NUMERIC`, `DECIMAL`, `MONEY`, `PRICE` 等都会赋予列 NUMERIC 亲和性。
    * **无损原则:** 这是理解 NUMERIC 的关键，它比 INTEGER 或 REAL 的转换规则更保守，优先保证数据不失真。
    * **避坑:** 正是因为它的灵活性，你无法保证从 NUMERIC 列里取出的值一定是数字。在应用程序代码中处理之前，最好还是检查一下它的实际类型。

* **【思考时间】:** 你觉得 SQLite 设计 NUMERIC 这种“善变”的亲和性，最大的好处和最大的潜在风险分别是什么？