KingbaseES数据类型完全指南:从基础CHAR到JSON/XML/几何类型

前言

你有没有遇到过这种情况------建表的时候随便选了个VARCHAR,结果存金额的时候精度丢了;或者存IP地址用了字符串,查询的时候怎么都筛不准?
说白了,这些问题都跟数据类型的选择有关。选对类型,数据存得准、查得快;选错了,小则浪费空间,大则数据失真。
这篇文章我把数据库里所有内置数据类型都梳理了一遍,从最常用的字符、数值,到JSON、XML、几何类型,每个都配上了实际能跑的代码。不管你是刚入门还是已经写过不少SQL,都能用得上。

文章目录

  • 前言
    • 一、字符类型:存文本,这几个就够了
      • [1.1 CHAR------固定长度,不够就补空格](#1.1 CHAR——固定长度,不够就补空格)
      • [1.2 VARCHAR2------用多少占多少,最灵活](#1.2 VARCHAR2——用多少占多少,最灵活)
      • [1.3 NCHAR与NVARCHAR2------多语言场景的标配](#1.3 NCHAR与NVARCHAR2——多语言场景的标配)
    • 二、数值类型:钱用NUMBER,计数用INT
      • [2.1 NUMBER------万能数值,精度你说了算](#2.1 NUMBER——万能数值,精度你说了算)
      • [2.2 INT与SMALLINT------存整数,轻量又高效](#2.2 INT与SMALLINT——存整数,轻量又高效)
      • [2.3 浮点类型------科学计算用,别拿来存钱](#2.3 浮点类型——科学计算用,别拿来存钱)
    • 三、日期时间类型:不只是"存个日期"那么简单
      • [3.1 DATE------基本的日期+时间](#3.1 DATE——基本的日期+时间)
      • [3.2 TIMESTAMP------精确到毫秒甚至微秒](#3.2 TIMESTAMP——精确到毫秒甚至微秒)
      • [3.3 带时区的时间戳------跨时区应用的必备](#3.3 带时区的时间戳——跨时区应用的必备)
      • [3.4 INTERVAL------表示"一段时间"](#3.4 INTERVAL——表示"一段时间")
    • 四、大对象类型:图片、长文、视频往里塞
      • [4.1 BLOB------二进制文件专用](#4.1 BLOB——二进制文件专用)
      • [4.2 CLOB与NCLOB------超长文本的归宿](#4.2 CLOB与NCLOB——超长文本的归宿)
      • [4.3 BFILE------不存文件,只存"文件在哪"](#4.3 BFILE——不存文件,只存"文件在哪")
    • 五、RAW类型:二进制数据"原样搬运"
    • 六、JSON类型:灵活数据的存储利器
      • [6.1 JSON和JSONB到底选哪个?](#6.1 JSON和JSONB到底选哪个?)
      • [6.2 JSON基本操作](#6.2 JSON基本操作)
      • [6.3 JSONB查询:包含、存在、嵌套](#6.3 JSONB查询:包含、存在、嵌套)
      • [6.4 给JSONB建索引,查询飞起来](#6.4 给JSONB建索引,查询飞起来)
      • [6.5 JSONPATH------更灵活的路径查询](#6.5 JSONPATH——更灵活的路径查询)
    • 七、XML类型:结构化文档怎么存
      • [7.1 XML基础用法](#7.1 XML基础用法)
      • [7.2 XMLType------更强大的XML操作](#7.2 XMLType——更强大的XML操作)
    • 八、几何类型:坐标、区域、距离计算
      • [8.1 几何计算示例](#8.1 几何计算示例)
    • 九、网络地址类型:别再用字符串存IP了
      • [9.1 inet与cidr](#9.1 inet与cidr)
      • [9.2 MAC地址](#9.2 MAC地址)
    • 十、全文搜索:让数据库帮你"找文章"
      • [10.1 tsvector------把文本变成可搜索的格式](#10.1 tsvector——把文本变成可搜索的格式)
      • [10.2 tsquery------构建搜索条件](#10.2 tsquery——构建搜索条件)
      • [10.3 实战:创建全文搜索系统](#10.3 实战:创建全文搜索系统)
    • 十一、范围类型:优雅地表达"从A到B"
      • [11.1 内置的6种范围类型](#11.1 内置的6种范围类型)
      • [11.2 范围操作实战](#11.2 范围操作实战)
      • [11.3 自定义范围类型](#11.3 自定义范围类型)
    • 十二、其他实用类型
      • [12.1 ROWID------每一行的"身份证号"](#12.1 ROWID——每一行的"身份证号")
      • [12.2 用户自定义类型](#12.2 用户自定义类型)
    • 总结:一张表选对数据类型

一、字符类型:存文本,这几个就够了

字符类型是打交道最多的,存名字、存地址、存备注都离不开它。说简单点,就是数据库里专门用来"装文字"的容器。

数据库同时支持单字节和多字节字符集,中英文混合存储完全没问题。

1.1 CHAR------固定长度,不够就补空格

CHAR是定长字符串。什么意思呢?你定义一个CHAR(10),不管往里塞"abc"还是"一二三四五",数据库都会把内容补齐到10个字符长度------不够的用空格填。

语法很简单:

sql 复制代码
CHAR [(size [BYTE | CHAR])]

size最大能到2000。这里有个容易踩坑的地方:长度单位到底是"字节"还是"字符"?

sql 复制代码
-- 按字符算:一个中文算1个字符,一个英文也算1个字符
CREATE TABLE test_char (col CHAR(4 CHAR));
INSERT INTO test_char VALUES ('一二三四');   -- 没问题,刚好4个字符
INSERT INTO test_char VALUES ('一二三四五'); -- 报错!超了1个字符

-- 按字节算:一个中文通常占3个字节,一个英文占1个字节
CREATE TABLE test_byte (col CHAR(4 BYTE));
INSERT INTO test_byte VALUES ('1234');  -- 没问题,4个ASCII字符=4字节
INSERT INTO test_byte VALUES ('一二三'); -- 报错!3个中文=9字节,远超4字节限制

什么时候该用CHAR?当你的数据长度基本一致的时候------手机号、身份证号、固定编码,这类场景用CHAR最合适,查询效率也更高。

1.2 VARCHAR2------用多少占多少,最灵活

VARCHAR2是日常开发中用得最多的变长字符串类型。跟CHAR不同,它不会用空格去填充,实际内容占多少就存多少。

sql 复制代码
VARCHAR2(size [BYTE | CHAR])

最大能存多长?这取决于一个系统参数MAX_STRING_SIZE

参数值 最大长度
STANDARD 4000字节
EXTENDED 32767字节

来看个实际的例子:

sql 复制代码
CREATE TABLE user_info (
    user_id   INT,
    username  VARCHAR2(50 CHAR),    -- 最多50个字符
    email     VARCHAR2(100 BYTE),   -- 最多100个字节
    bio       VARCHAR2(500 CHAR)    -- 个人简介,最多500个字符
);

INSERT INTO user_info VALUES (1, '张三', 'zhangsan@example.com', '全栈开发工程师');

-- VARCHAR2会原样存储,不会补空格
SELECT username, length(username) FROM user_info;
-- 结果:张三 | 2(实际字符长度)

顺便提一句,VARCHAR和VARCHAR2功能完全一样,可以互换使用。另外CHARACTER VARYING、CHAR VARYING也都是它的别名。

1.3 NCHAR与NVARCHAR2------多语言场景的标配

如果你的应用要同时支持中英日韩多国语言,那就得请出NCHAR和NVARCHAR2了。它们用Unicode国际字符集存储,不管什么语言的字符都能正确保存,不会出现乱码。

sql 复制代码
-- NCHAR:定长的Unicode字符串
-- NVARCHAR2:变长的Unicode字符串
CREATE TABLE multilang (
    label    NCHAR(20),            -- 固定20个Unicode字符位
    content  NVARCHAR2(500 CHAR)   -- 变长,最多500个Unicode字符
);

-- 中英日文混合存储,完全没问题
INSERT INTO multilang VALUES ('Hello世界こんにちは', '多语言内容存储示例');

SELECT label, content FROM multilang;

两者的容量限制:

类型 编码方式 最大字符数
NCHAR AL16UTF16 1000字符
NCHAR UTF8 2000字符
NVARCHAR2 EXTENDED模式 最大32767字节
NVARCHAR2 STANDARD模式 最大4000字节

简单总结一下字符类型的选择思路:长度固定用CHAR,长度变化用VARCHAR2,涉及多语言就用NCHAR/NVARCHAR2。

二、数值类型:钱用NUMBER,计数用INT

数值类型看起来简单,但选错了后果很严重------尤其是涉及钱的时候。

数据库能存整数、小数、正数、负数、零,甚至还有几个特殊值:Infinity(正无穷)、-Infinity(负无穷)和NaN(不是数字)。

2.1 NUMBER------万能数值,精度你说了算

NUMBER是最通用的数值类型,你可以精确控制它保留多少位有效数字、多少位小数。

sql 复制代码
NUMBER [(precision [, scale])]
  • precision(精度):有效数字总共多少位,范围1~1000
  • scale(标度):小数点后保留几位,范围-84~1000。标度还能是负数,表示从个位开始往左舍入

直接看例子最直观:

sql 复制代码
CREATE TABLE financial_records (
    record_id   NUMBER(10),        -- 10位整数,足够存ID
    unit_price  NUMBER(8,2),       -- 8位有效数字、2位小数,如 999999.99
    total       NUMBER(12,2),      -- 12位有效数字、2位小数,适合存大额金额
    round_value NUMBER(12,-2)      -- 标度为负,自动舍入到百位
);

-- 实际插入数据
INSERT INTO financial_records VALUES (100001, 29.90, 2990.00, 123456);
-- round_value 列存入123456,实际会被存为 123500(百位以下舍入)

SELECT * FROM financial_records;

精度和标度的效果演示:

sql 复制代码
-- 四舍五入到3位有效数字
SELECT CAST(123.89 AS NUMBER(3));      -- 输出:124

-- 保留2位小数
SELECT CAST(123.89 AS NUMBER(5,2));    -- 输出:123.89

-- 保留1位小数,第2位小数四舍五入
SELECT CAST(123.89 AS NUMBER(6,1));    -- 输出:123.9

-- 小数点后5位,适合存精度很高的数据
SELECT CAST(0.000127 AS NUMBER(4,5));  -- 输出:0.00013

-- 超出精度直接报错
SELECT CAST(123.89 AS NUMBER(3,2));
-- ERROR: 超出精度范围

2.2 INT与SMALLINT------存整数,轻量又高效

如果你要存的只是整数(比如ID、数量、年龄),那用INT或SMALLINT比NUMBER更高效:

类型 取值范围 占用空间
INT(别名INTEGER) -2,147,483,648 ~ 2,147,483,647 4字节
SMALLINT -32,768 ~ 32,767 2字节
sql 复制代码
CREATE TABLE orders (
    order_id   INT,            -- 订单编号,21亿以内够用了
    quantity   SMALLINT,       -- 购买数量,3万多个也够了
    status     SMALLINT        -- 状态码:0待付款 1已付款 2已发货 3已完成
);

INSERT INTO orders VALUES (100001, 5, 0);
INSERT INTO orders VALUES (100002, 120, 1);

SELECT * FROM orders WHERE quantity > 10;

经验法则:一般业务数据用INT就够了,如果是状态码、排序号这种小范围的值,SMALLINT更省空间。

2.3 浮点类型------科学计算用,别拿来存钱

FLOAT是可变精度的浮点数。当你需要存科学数据、传感器读数这类对"绝对精确"要求不高的数值时,浮点类型就派上用场了。

sql 复制代码
-- FLOAT:precision为1~24时是单精度(REAL),25~53时是双精度(DOUBLE PRECISION)
-- BINARY_FLOAT:32位单精度,范围 1.17549E-38 ~ 3.40282E+38
-- BINARY_DOUBLE:64位双精度,范围 2.22507E-308 ~ 1.79769E+308

实际使用:

sql 复制代码
CREATE TABLE sensor_data (
    sensor_id    INT,
    temperature  BINARY_FLOAT,    -- 温度传感器读数
    pressure     BINARY_DOUBLE,   -- 气压传感器读数
    recorded_at  TIMESTAMP
);

-- 浮点数的字面量写法
INSERT INTO sensor_data VALUES (1, 36.5F, 101325.0D, CURRENT_TIMESTAMP);
INSERT INTO sensor_data VALUES (2, -0.1F, 101200.5D, CURRENT_TIMESTAMP);

-- 查询浮点数据
SELECT sensor_id, temperature, pressure FROM sensor_data;

-- 浮点数支持特殊值
SELECT 'Infinity'::float8;   -- 正无穷
SELECT '-Infinity'::float8;  -- 负无穷
SELECT 'NaN'::float8;        -- 不是数字

重点提醒:浮点数用的是二进制存储,没法精确表示某些十进制小数(比如0.1)。所以,凡是跟钱沾边的,一律用NUMBER,千万别用FLOAT!

三、日期时间类型:不只是"存个日期"那么简单

时间看起来简单,但真要在业务里用好,得区分好几种不同的精度和场景。

3.1 DATE------基本的日期+时间

DATE存的是年、月、日、时、分、秒,从公元前4713年到公元5874897年,跨度大得离谱。

sql 复制代码
CREATE TABLE events (
    event_id   INT,
    event_name VARCHAR2(100),
    event_date DATE
);

-- 用TO_DATE把字符串转成DATE
INSERT INTO events VALUES (1, '项目启动', TO_DATE('2024-12-01', 'YYYY-MM-DD'));
INSERT INTO events VALUES (2, '年终总结', TO_DATE('2024-12-31 14:00:00', 'YYYY-MM-DD HH24:MI:SS'));

-- 不指定时间的话,默认是午夜 00:00:00
SELECT event_name, event_date FROM events;
-- 输出:
-- 项目启动 | 2024-12-01 00:00:00
-- 年终总结 | 2024-12-31 14:00:00

-- DATE之间可以直接做加减
SELECT event_name, event_date - TO_DATE('2024-12-01','YYYY-MM-DD') AS days_diff
FROM events;
-- 输出:
-- 项目启动 | 0
-- 年终总结 | 30

3.2 TIMESTAMP------精确到毫秒甚至微秒

TIMESTAMP是DATE的"加强版",在年月日时分秒之外,还能存小数秒,精度最高到6位(微秒级)。

sql 复制代码
TIMESTAMP [(fractional_seconds_precision)]  -- 小数秒位数,0~6,默认6
sql 复制代码
CREATE TABLE system_log (
    log_id    INT,
    log_time  TIMESTAMP(3),     -- 精确到毫秒(3位小数)
    level     VARCHAR2(10),
    message   VARCHAR2(500)
);

-- 插入带毫秒精度的时间
INSERT INTO system_log VALUES (
    1,
    TO_TIMESTAMP('2024-01-01 09:30:53.123', 'YYYY-MM-DD HH24:MI:SS.FF3'),
    'INFO',
    '系统启动完成'
);

INSERT INTO system_log VALUES (
    2,
    TO_TIMESTAMP('2024-01-01 09:30:53.456', 'YYYY-MM-DD HH24:MI:SS.FF3'),
    'ERROR',
    '数据库连接超时'
);

-- 精确到毫秒的查询
SELECT log_id, log_time, level, message FROM system_log
WHERE log_time > TO_TIMESTAMP('2024-01-01 09:30:53.200', 'YYYY-MM-DD HH24:MI:SS.FF3');

3.3 带时区的时间戳------跨时区应用的必备

如果你的用户分布在北京、纽约、伦敦,时间戳就得带上时区信息,不然时间就全乱套了。

数据库提供了两种带时区的TIMESTAMP:

  • TIMESTAMP WITH TIME ZONE:原样保存时区偏移,查询时显示带时区的时间
  • TIMESTAMP WITH LOCAL TIME ZONE:自动转换为当前会话时区显示
sql 复制代码
-- 场景:跨国会议安排
CREATE TABLE global_meetings (
    meeting_id   INT,
    topic        VARCHAR2(200),
    start_time   TIMESTAMP WITH TIME ZONE,         -- 保留原始时区
    end_time     TIMESTAMP WITH LOCAL TIME ZONE     -- 自动转本地时区
);

-- 北京时间下午3点开会
INSERT INTO global_meetings VALUES (
    1,
    '全球技术同步会',
    TIMESTAMP '2024-06-15 15:00:00 +08:00',
    TIMESTAMP '2024-06-15 17:00:00 +08:00'
);

-- 查询时能看到时区信息
SELECT meeting_id, topic, start_time FROM global_meetings;
-- 输出:1 | 全球技术同步会 | 2024-06-15 15:00:00 +08:00

3.4 INTERVAL------表示"一段时间"

INTERVAL不是某个时间点,而是一段时间的长度。比如"3个月"、"2天4小时30分钟"这种。

它有两种形式:

sql 复制代码
-- INTERVAL YEAR TO MONTH:以"年+月"为单位
-- INTERVAL DAY TO SECOND:以"天+时+分+秒"为单位

-- 例子:2004年闰年2月29日,加4年后是哪天?
SELECT TO_DATE('29-FEB-2004', 'DD-MON-YYYY') + TO_YMINTERVAL('4-0') AS result;
-- 输出:2008-02-29 00:00:00

-- 实际建表使用
CREATE TABLE project_schedule (
    project_name VARCHAR2(100),
    start_time   TIMESTAMP,
    buffer_time  INTERVAL DAY(3) TO SECOND(0),   -- 缓冲时间,精确到天和秒
    duration     INTERVAL YEAR TO MONTH            -- 项目周期,以年月为单位
);

INSERT INTO project_schedule VALUES (
    'ERP系统建设',
    TO_TIMESTAMP('2024-03-01 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
    '0 12:30:00',    -- 半天缓冲
    '1-6'            -- 1年6个月
);

SELECT project_name,
       start_time,
       buffer_time,
       duration,
       start_time + buffer_time AS adjusted_start
FROM project_schedule;

最后来个快速对照表:

类型 什么时候用 典型场景
DATE 不需要毫秒精度 生日、节假日、订单日期
TIMESTAMP 需要精确到毫秒/微秒 系统日志、审计追踪
TIMESTAMP WITH TIME ZONE 用户跨越多个时区 国际会议、全球化系统
INTERVAL 算时间差或设偏移 项目排期、定时任务

四、大对象类型:图片、长文、视频往里塞

普通字符串存几万字还行,但如果你要存一张高清图片或者一部视频,那就要靠LOB(Large Object)类型了。

4.1 BLOB------二进制文件专用

BLOB专门用来存二进制格式的大文件------图片、音频、视频都可以,最大能存到1GB。

sql 复制代码
CREATE TABLE media_library (
    file_id     INT,
    file_name   VARCHAR2(200),
    file_type   VARCHAR2(20),    -- image/audio/video
    file_size   INT,             -- 文件大小(字节)
    file_data   BLOB             -- 实际文件内容
);

-- 插入一条记录(实际开发中通常通过程序写入BLOB数据)
INSERT INTO media_library (file_id, file_name, file_type, file_size, file_data)
VALUES (1, 'banner.jpg', 'image', 204800, empty_blob());

4.2 CLOB与NCLOB------超长文本的归宿

  • CLOB:用数据库字符集存大段文本,最大1GB
  • NCLOB:用Unicode国际字符集存大段文本,最大1GB
sql 复制代码
CREATE TABLE knowledge_base (
    article_id  INT PRIMARY KEY,
    title       VARCHAR2(200),
    author      VARCHAR2(50),
    content     CLOB,            -- 文章正文,几万字不在话下
    summary     NCLOB,           -- Unicode格式的摘要
    created_at  DATE
);

INSERT INTO knowledge_base VALUES (
    1,
    '数据库性能优化实战',
    '技术团队',
    '本文详细介绍了数据库性能优化的各个方面,包括索引设计、查询优化、缓存策略......(此处省略一万字)',
    '从索引到缓存,全方位讲解性能优化方法',
    TO_DATE('2024-06-01', 'YYYY-MM-DD')
);

-- CLOB也能用LIKE搜索
SELECT title FROM knowledge_base WHERE content LIKE '%索引设计%';

4.3 BFILE------不存文件,只存"文件在哪"

BFILE比较特殊------它不存文件内容,只存一个指向操作系统文件的"指针"。而且只能读,不能改。

sql 复制代码
-- 先创建一个目录对象(相当于给文件路径起个别名)
CREATE DIRECTORY doc_dir AS '/data/documents';

-- 建表
CREATE TABLE external_docs (
    doc_id    INT,
    doc_name  VARCHAR2(200),
    doc_ref   BFILE           -- 指向外部文件的引用
);

-- 用BFILENAME函数插入文件引用
INSERT INTO external_docs VALUES (1, '年度报告', BFILENAME('doc_dir', 'annual_report.pdf'));
INSERT INTO external_docs VALUES (2, '技术规范', BFILENAME('doc_dir', 'tech_spec.docx'));

-- 查询时看到的是引用信息,不是文件内容
SELECT doc_id, doc_name FROM external_docs;

使用建议:BFILE适合存那些"体积大但不需要在数据库里修改"的文件,比如归档的PDF、历史扫描件等。

五、RAW类型:二进制数据"原样搬运"

RAW类型用来存二进制数据,它最实用的特点是:在不同字符集的数据库或客户端之间传输时,数据不会被"自作主张"地转换编码。

sql 复制代码
RAW [(size)]     -- size范围1~32767字节
LONG RAW         -- 跟RAW一样,但不能指定长度
sql 复制代码
CREATE TABLE raw_data (
    id         INT,
    bin_data   RAW(1024),       -- 最多1024字节的二进制数据
    signature  RAW(256)         -- 256字节的数字签名
);

-- 用HEXTORAW把十六进制字符串转成RAW
INSERT INTO raw_data VALUES (1, HEXTORAW('0A1B2C3D4E5F'), HEXTORAW('AABBCCDD'));

-- 用RAWTOHEX把RAW转回十六进制查看
SELECT id, RAWTOHEX(bin_data) AS hex_data FROM raw_data;
-- 输出:1 | 0A1B2C3D4E5F

什么时候用RAW?加密数据、通信协议数据、设备原始报文------这类"就是一串字节,别给我做任何转换"的场景。

六、JSON类型:灵活数据的存储利器

现在几乎所有应用都在用JSON------配置信息、API返回值、动态表单,到处都是。数据库原生支持JSON,不用再把JSON当普通字符串存了。

数据库提供了两种JSON类型:JSONJSONB

6.1 JSON和JSONB到底选哪个?

这是最常被问到的问题。简单说:

  • JSON:原样存储,存得快,但每次查询都要重新解析
  • JSONB:存的时候就解析成二进制,占空间稍大,但查询快得多,还支持索引
对比项 JSON JSONB
存储方式 原始文本 二进制格式
空格处理 原样保留 自动去掉
键的顺序 保持输入顺序 自动排序
重复键 全部保留 只留最后一个
查询速度 每次要重新解析 直接查,快
能建索引吗 不能 能(GIN索引)

结论:绝大多数场景下,直接用JSONB就对了。

6.2 JSON基本操作

sql 复制代码
-- 创建一张存JSON数据的表
CREATE TABLE api_data (
    id    INT,
    jdoc  JSONB
);

-- 插入一条JSON数据
INSERT INTO api_data VALUES (1, '{
    "guid": "9c36adc1-7fb5-4d5b-83b4-90356a46061a",
    "name": "张三",
    "is_active": true,
    "score": 95.5,
    "tags": ["developer", "architect", "leader"]
}');

-- 再插一条
INSERT INTO api_data VALUES (2, '{
    "guid": "7f3b2a01-4e9c-4d2f-b8a1-6234567890ab",
    "name": "李四",
    "is_active": false,
    "score": 88.0,
    "tags": ["designer", "leader"]
}');

-- 用 -> 提取JSON对象(返回JSON类型)
-- 用 ->> 提取JSON对象(返回文本)
SELECT
    jdoc->>'name'   AS name,
    jdoc->'score'   AS score,
    jdoc->'is_active' AS active
FROM api_data;

-- 输出:
--  name | score | active
-- ------+-------+--------
--  张三 | 95.5  | true
--  李四 | 88.0  | false

6.3 JSONB查询:包含、存在、嵌套

JSONB的查询能力非常强大,几个操作符就能搞定大部分需求:

sql 复制代码
-- @> 包含查询:tags里有没有"leader"?
SELECT jdoc->>'name' AS name FROM api_data
WHERE jdoc @> '{"tags":["leader"]}'::jsonb;
-- 输出:张三 和 李四 都有leader标签

-- ? 存在查询:有没有某个键?
SELECT jdoc->>'name' AS name FROM api_data
WHERE jdoc ? 'is_active';
-- 输出:两条都有

-- ?| 任一存在:有没有tags或者email?
SELECT jdoc->>'name' AS name FROM api_data
WHERE jdoc ?| array['tags', 'email'];

-- ?& 全部存在:是不是同时有name和score?
SELECT jdoc->>'name' AS name FROM api_data
WHERE jdoc ?& array['name', 'score'];

嵌套查询也不在话下:

sql 复制代码
-- 更复杂的嵌套数据
INSERT INTO api_data VALUES (3, '{
    "name": "王五",
    "tags": [
        {"term": "backend", "level": "senior"},
        {"term": "cloud", "level": "expert"}
    ]
}');

-- 查找tags中包含特定term的文档
SELECT jdoc->>'name' FROM api_data
WHERE jdoc @> '{"tags":[{"term":"backend"}]}';

6.4 给JSONB建索引,查询飞起来

数据量一大,JSONB查询也会变慢。这时候就得靠GIN索引了:

sql 复制代码
-- 通用GIN索引,支持 @>、?、?&、?| 操作符
CREATE INDEX idx_jsonb ON api_data USING gin (jdoc);

-- jsonb_path_ops索引:只支持@>操作符,但更小更快
CREATE INDEX idx_jsonb_path ON api_data USING gin (jdoc jsonb_path_ops);

-- 针对特定键的表达式索引(最精准)
CREATE INDEX idx_jsonb_tags ON api_data USING gin ((jdoc -> 'tags'));

建完索引后,包含查询就能走索引了,速度快很多:

sql 复制代码
-- 这个查询会走 idx_jsonb_tags 索引
SELECT jdoc->>'name', jdoc->'score' FROM api_data
WHERE jdoc -> 'tags' ? 'leader';

6.5 JSONPATH------更灵活的路径查询

除了基本的操作符,还可以用JSONPATH语法做更高级的查询:

sql 复制代码
-- 用 @@ 操作符配合JSONPATH语法
SELECT jdoc->>'name' FROM api_data
WHERE jdoc @@ '$.tags[*] == "leader"';

-- 带条件的路径查询
SELECT jdoc->>'name' FROM api_data
WHERE jdoc @@ '$.score ? (@ > 90)';
-- 输出:张三(score=95.5)

七、XML类型:结构化文档怎么存

XML在金融、政务、企业集成这些领域用得很多。相比把XML随便塞进一个文本字段,用专门的XML类型有个好处------存储时会自动检查XML结构合不合法。

7.1 XML基础用法

sql 复制代码
CREATE TABLE xml_docs (
    doc_id   INT,
    content  XML
);

-- 方式一:用XMLPARSE创建
INSERT INTO xml_docs VALUES (1,
    XMLPARSE(DOCUMENT '<?xml version="1.0"?>
    <book>
        <title>数据库入门</title>
        <chapter id="1">基础概念</chapter>
        <chapter id="2">进阶技巧</chapter>
    </book>')
);

-- 方式二:直接类型转换
INSERT INTO xml_docs VALUES (2, '<note><to>张三</to><body>明天开会</body></note>'::xml);

-- 查询XML内容
SELECT doc_id, content FROM xml_docs;

-- 把XML转回字符串
SELECT XMLSERIALIZE(DOCUMENT content AS text) FROM xml_docs WHERE doc_id = 1;

-- 判断是完整文档还是文档片段
SELECT content IS DOCUMENT AS is_full_doc FROM xml_docs;

7.2 XMLType------更强大的XML操作

XMLType提供了一套方法,可以用XPath表达式在XML里精准定位和提取数据:

sql 复制代码
CREATE TABLE xml_orders (
    order_id INT,
    order_data XMLType
);

-- 插入XML格式的订单数据
INSERT INTO xml_orders VALUES (1, XMLType('<?xml version="1.0"?>
<Order>
    <Customer>李四</Customer>
    <Items>
        <Item sku="A001">
            <Name>键盘</Name>
            <Price>299</Price>
            <Qty>2</Qty>
        </Item>
        <Item sku="A002">
            <Name>鼠标</Name>
            <Price>89</Price>
            <Qty>1</Qty>
        </Item>
    </Items>
    <Total>687</Total>
</Order>'));

-- 使用XMLType的方法查询
SELECT x.order_data FROM xml_orders x;

注意:XML类型没有比较操作符,不能直接在上面建索引。需要快速搜索的话,要在XPath表达式上建函数索引。

八、几何类型:坐标、区域、距离计算

这组类型可能日常业务不太常用,但在地图应用、CAD设计、游戏开发这些场景中就非常关键了。

数据库内置了7种二维几何类型:

类型 占多大 干什么用 怎么写
point 16字节 平面上的一个点 (x, y)
line 32字节 无限长的直线 {A, B, C}
lseg 32字节 一段线段 ((x1,y1),(x2,y2))
box 32字节 矩形 ((x1,y1),(x2,y2))
path 16+16n字节 路径(开放或封闭) [(x1,y1),...]或((x1,y1),...)
polygon 40+16n字节 多边形 ((x1,y1),...)
circle 24字节 <(x,y),r>
sql 复制代码
-- 建一张带几何字段的表
CREATE TABLE locations (
    name      VARCHAR2(50),
    center    point,          -- 中心坐标
    area      polygon,        -- 区域多边形
    boundary  circle          -- 圆形边界
);

INSERT INTO locations VALUES (
    '总部大楼',
    '(116.397, 39.908)',                                -- 一个点
    '((116.39, 39.90), (116.40, 39.90),
      (116.40, 39.91), (116.39, 39.91))',               -- 四边形
    '<(116.397, 39.908), 0.005>'                        -- 以点为圆心的圆
);

SELECT name, center, area, boundary FROM locations;

8.1 几何计算示例

sql 复制代码
-- 计算两点之间的距离
SELECT point '(1,1)' <-> point '(4,5)' AS distance;
-- 输出:5(勾股定理:√(3²+4²) = 5)

-- 判断一个点是否在矩形内
SELECT box '((0,0),(2,2))' @> point '(1,1)' AS is_inside;
-- 输出:true

SELECT box '((0,0),(2,2))' @> point '(3,3)' AS is_inside;
-- 输出:false

-- 计算圆的面积
SELECT area(circle '<(0,0), 5>') AS circle_area;
-- 输出:78.5398163397448(π × 5²)

-- 两个矩形是否重叠
SELECT box '((0,0),(2,2))' && box '((1,1),(3,3))' AS overlaps;
-- 输出:true

-- 计算两点之间的线段中点
SELECT center(lseg '((0,0),(4,4))') AS midpoint;
-- 输出:(2,2)

九、网络地址类型:别再用字符串存IP了

很多人图省事,用VARCHAR存IP地址。但字符串存IP有个大问题------你没法判断"192.168.1.100是不是属于192.168.1.0/24这个网段",除非你自己写一大堆逻辑。

用专门的inet和cidr类型,这些事数据库帮你做了。

类型 占多大 存什么
cidr 7或19字节 IPv4/IPv6网络地址
inet 7或19字节 IPv4/IPv6主机地址
macaddr 6字节 MAC地址
macaddr8 8字节 MAC地址(EUI-64格式)

9.1 inet与cidr

sql 复制代码
CREATE TABLE network_assets (
    asset_name  VARCHAR2(50),
    ip_addr     inet,
    subnet      cidr
);

INSERT INTO network_assets VALUES ('web-server-1', '192.168.1.100', '192.168.1.0/24');
INSERT INTO network_assets VALUES ('db-server',    '10.0.0.5/16',    '10.0.0.0/16');
INSERT INTO network_assets VALUES ('cache-server', '172.16.5.20',    '172.16.0.0/16');

-- 检查IP是否属于某个网段(<< 表示"包含在")
SELECT asset_name FROM network_assets WHERE ip_addr << '192.168.1.0/24'::cidr;
-- 输出:web-server-1

-- 获取IP的网络部分
SELECT network('192.168.1.100/24');
-- 输出:192.168.1.0/24

-- 获取主机部分
SELECT host('192.168.1.100/24');
-- 输出:192.168.1.100

-- 判断两个网段是否重叠
SELECT inet '192.168.1.0/24' && inet '192.168.1.128/25' AS overlaps;
-- 输出:true

inet和cidr的区别:inet允许"非标准"写法(比如192.168.0.1/24,主机位不是全0),cidr则要求严格的网络地址。

9.2 MAC地址

sql 复制代码
CREATE TABLE devices (
    device_id  INT,
    device_name VARCHAR2(50),
    mac        macaddr
);

-- 下面这几种写法都是同一个MAC地址
INSERT INTO devices VALUES (1, '服务器A', '08:00:2b:01:02:03');
INSERT INTO devices VALUES (2, '服务器B', '08-00-2b-01-02-03');
INSERT INTO devices VALUES (3, '交换机',  '08002b:010203');
INSERT INTO devices VALUES (4, '路由器',  '0800.2b01.0203');
INSERT INTO devices VALUES (5, '防火墙',  '08002b010203');

-- 查询时统一输出为冒号分隔格式
SELECT device_id, device_name, mac FROM devices;
-- 输出全部显示为 08:00:2b:01:02:03

-- EUI-64格式的MAC地址(8字节)
INSERT INTO devices VALUES (6, '无线AP', '08:00:2b:01:02:03:04:05'::macaddr8);

-- 把48位MAC转成EUI-64格式
SELECT macaddr8_set7bit('08:00:2b:01:02:03');
-- 输出:0a:00:2b:ff:fe:01:02:03

十、全文搜索:让数据库帮你"找文章"

如果你要在大量文本中搜索关键词,LIKE '%关键词%' 也能凑合用,但性能很差,而且没法按相关度排序。全文搜索类型就是专门解决这个问题的。

它靠两个类型配合工作:

  • tsvector:把文档文本拆解、去重、排序后存储的"词袋"
  • tsquery:你要搜索的关键词组合

10.1 tsvector------把文本变成可搜索的格式

sql 复制代码
-- 直接转tsvector:自动去重和排序
SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector;
-- 输出:'a' 'and' 'ate' 'cat' 'fat' 'mat' 'on' 'rat' 'sat'
-- 注意:重复的a和fat只出现一次

-- 用to_tsvector进行语言相关的处理(推荐)
SELECT to_tsvector('english', 'The Fat Rats');
-- 输出:'fat':2 'rat':3
-- "The"被过滤(停用词),"Fat"变成小写"fat","Rats"变成词干"rat"
-- 冒号后面的数字是词在原文中的位置

10.2 tsquery------构建搜索条件

tsquery支持布尔操作符来组合搜索词:

操作符 含义 例子
& AND,同时包含 fat & rat
| OR,包含任一 fat | cat
! NOT,不包含 fat & !cat
<-> 紧跟在后面 quick <-> fox
sql 复制代码
-- 简单AND查询
SELECT 'fat & rat'::tsquery;
-- 输出:'fat' & 'rat'

-- 组合查询
SELECT 'fat & (rat | cat)'::tsquery;
-- 输出:'fat' & ( 'rat' | 'cat' )

-- 排除查询
SELECT 'fat & rat & !cat'::tsquery;
-- 输出:'fat' & 'rat' & !'cat'

-- 前缀匹配
SELECT 'super:*'::tsquery;
-- 会匹配superman、supernatural等所有以super开头的词

10.3 实战:创建全文搜索系统

sql 复制代码
-- 建表
CREATE TABLE articles (
    article_id  INT,
    title       VARCHAR2(200),
    body        TEXT
);

INSERT INTO articles VALUES (1, '数据库性能优化',
    '数据库性能优化是每个开发者的必修课。索引设计、查询优化、缓存策略都是关键技能。');
INSERT INTO articles VALUES (2, 'Python入门教程',
    'Python是一门非常适合初学者的编程语言。它的语法简洁,社区活跃。');
INSERT INTO articles VALUES (3, '分布式数据库架构',
    '分布式数据库通过数据分片和复制来提高系统的可用性和性能。');

-- 创建GIN索引加速全文搜索
CREATE INDEX idx_articles_body ON articles
USING gin (to_tsvector('english', body));

-- 搜索:找出提到"database"和"performance"的文章
SELECT title FROM articles
WHERE to_tsvector('english', body) @@ to_tsquery('database & performance');

-- 搜索:找出提到"database"或"python"的文章
SELECT title FROM articles
WHERE to_tsvector('english', body) @@ to_tsquery('database | python');

-- 按相关度排序(使用ts_rank)
SELECT title, ts_rank(to_tsvector('english', body), to_tsquery('database')) AS rank
FROM articles
WHERE to_tsvector('english', body) @@ to_tsquery('database')
ORDER BY rank DESC;

十一、范围类型:优雅地表达"从A到B"

生活中到处都是"区间"的概念------价格区间、日期区间、年龄范围。以前你可能用两个字段(start和end)来表示,现在可以用一个范围类型搞定,而且天然支持"是否重叠"、"是否包含"这类查询。

11.1 内置的6种范围类型

类型 子类型 说明
int4range integer 整数范围
int8range bigint 大整数范围
numrange numeric 小数范围
tsrange timestamp 时间戳范围(无时区)
tstzrange timestamptz 时间戳范围(带时区)
daterange date 日期范围

11.2 范围操作实战

范围用方括号[]表示包含边界,用圆括号()表示不包含边界:

sql 复制代码
-- 会议室预约系统
CREATE TABLE room_booking (
    room_id   INT,
    booked_by VARCHAR2(50),
    during    tsrange
);

INSERT INTO room_booking VALUES
    (101, '张三', '[2025-01-15 09:00, 2025-01-15 10:00)'),   -- 9点到10点
    (101, '李四', '[2025-01-15 10:00, 2025-01-15 11:30)'),   -- 10点到11点半
    (102, '王五', '[2025-01-15 09:30, 2025-01-15 12:00)');   -- 9点半到12点

-- 检查101会议室有没有时间冲突
SELECT booked_by FROM room_booking
WHERE room_id = 101
  AND during && '[2025-01-15 09:30, 2025-01-15 10:30)'::tsrange;
-- 输出:张三(他的9:00-10:00跟9:30-10:30有重叠)

-- 包含检查:15在不在[10,20)范围内?
SELECT int4range(10, 20) @> 15;
-- 输出:true

-- 重叠检查:两个范围有没有交集?
SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0);
-- 输出:true(11.1~22.2 跟 20.0~30.0 在20.0~22.2处重叠)

-- 取交集
SELECT int4range(10, 20) * int4range(15, 25);
-- 输出:[15,20)

-- 取上界和下界
SELECT lower(int4range(10, 20)), upper(int4range(10, 20));
-- 输出:10 | 20

-- 范围是否为空
SELECT isempty(numrange(1, 5));
-- 输出:false

11.3 自定义范围类型

如果内置的几种不够用,你可以自己定义:

sql 复制代码
-- 创建一个浮点数范围类型
CREATE TYPE floatrange AS RANGE (
    subtype = float8,
    subtype_diff = float8mi
);

-- 直接使用
SELECT '[1.234, 5.678]'::floatrange;
-- 输出:[1.234,5.678)

-- 也可以创建一个时间范围类型
CREATE FUNCTION time_subtype_diff(x time, y time) RETURNS float8 AS
$$ SELECT EXTRACT(EPOCH FROM (x - y)) $$ LANGUAGE sql STRICT IMMUTABLE;

CREATE TYPE timerange AS RANGE (
    subtype = time,
    subtype_diff = time_subtype_diff
);

-- 使用自定义时间范围
SELECT '[11:10, 23:00]'::timerange;
-- 输出:[11:10:00,23:00:00)

十二、其他实用类型

12.1 ROWID------每一行的"身份证号"

ROWID是数据库给表中每一行自动分配的逻辑标识。它以23个16进制字符展示,是单调递增的------后插入的数据ROWID一定更大。

sql 复制代码
-- 查看每一行的ROWID
SELECT ROWID, employee_id, name FROM employees WHERE employee_id <= 5;

-- 用ROWID定位特定行(比用主键还快)
SELECT * FROM employees WHERE ROWID = 'AAAACMAABAAAAEqAAA';

-- ROWID支持比较操作符
SELECT ROWID, name FROM employees
WHERE ROWID > 'AAAACMAABAAAAEqAAA'
ORDER BY ROWID;

12.2 用户自定义类型

当内置类型没法满足你的业务模型时,可以自己定义类型:

sql 复制代码
-- 定义一个对象类型:地址
CREATE TYPE address_type AS OBJECT (
    province   VARCHAR2(50),
    city       VARCHAR2(50),
    street     VARCHAR2(200),
    post_code  CHAR(6)
);

-- 在表中使用自定义类型
CREATE TABLE customers (
    customer_id  INT,
    name         VARCHAR2(50),
    home_addr    address_type
);

-- 可变数组(Varray):一组有序元素,需指定最大数量
-- 嵌套表(Nested Table):一组无序元素

总结:一张表选对数据类型

你要存什么 用这个类型 常见场景
长度固定的文本 CHAR 手机号、邮编、编码
长度不固定的文本 VARCHAR2 姓名、地址、备注
多语言文本 NVARCHAR2 国际化应用的界面文本
精确小数(尤其是钱) NUMBER(p,s) 价格、金额、折扣率
整数 INT / SMALLINT ID、数量、年龄、状态码
科学数据、传感器值 BINARY_FLOAT / BINARY_DOUBLE 温度、气压、坐标值
日期(不要求毫秒) DATE 生日、订单日期
精确时间戳 TIMESTAMP 日志、审计记录
跨时区时间 TIMESTAMP WITH TIME ZONE 全球化系统
大段文本 CLOB / NCLOB 文章、合同、日志文件
图片、音视频 BLOB 多媒体文件存储
二进制数据 RAW 加密数据、协议报文
灵活结构的数据 JSONB 配置信息、动态属性
XML文档 XML / XMLType 金融报文、政务数据
平面坐标、区域 point / polygon / circle 地图标记、区域范围
IP地址 inet / cidr 网络设备管理
MAC地址 macaddr 设备管理
文本关键词搜索 tsvector + tsquery 文档搜索、内容检索
区间范围 各类range类型 排期、定价区间

最后记住三个选类型的原则:

  1. 精确优先:能用NUMBER就别用FLOAT,尤其是算钱的时候
  2. 够用就好:能用SMALLINT就别用INT,能用VARCHAR2(50)就别用VARCHAR2(4000),省空间就是省性能
  3. 语义匹配:IP地址别用字符串,时间别用字符串,让数据库帮你做数据校验
相关推荐
Irene19911 小时前
大数据开发场景下,总结并翻译 Oracle 中常见的错误(补充其他错误码:适合初学者)
大数据·oracle
摇滚侠11 小时前
expdp 查看帮助
java·数据库·oracle
zxrhhm14 小时前
Oracle 索引完整指南
数据库·oracle
折哥的程序人生 · 物流技术专研16 小时前
从“卡死”到“秒过”:WMS销售数据跨库回填的极限优化之旅
数据库·机器学习·oracle
马优晨17 小时前
oracle 的 Schema
数据库·oracle·oracle的schema·数据库的 schema·oracle的schema数据
归途醉染19 小时前
Swifter.Json
c#·json·swifter.json
摇滚侠20 小时前
Oracle19c 导出 Oracle11g 导入,Oracle19c 导出导入,Oracle11g 导出导入
java·数据库·oracle
Irene199120 小时前
(课堂笔记)Oracle:场景判断(CASE WHEN)、集合运算、EXISTS、行列转换
oracle
网络点点滴20 小时前
NPM 和 package.json 文件简介
前端·npm·json