写在前面
做国产数据库迁移这几年,跟金仓数据库(KingbaseES)打交道的机会越来越多。说实话,刚开始从 MySQL 或者 Oracle 切过来的时候,确实有些不习惯——有些语法看着像,但细节上总给你埋点小坑;有些功能换个名字,找半天找不着。这篇文章不跟你聊那些虚的架构设计,就踏踏实实把日常开发运维中最常用的 SQL 语法和实操技巧捋一遍。如果你是有 SQL 基础的开发者,或者是正在从 MySQL、Oracle 往金仓迁移的工程师,看完这篇应该能少走不少弯路。
金仓数据库是电科金仓旗下的商业数据库产品,企业级场景里用得很多,尤其是政务、金融这些对国产化有明确要求的领域。它不是开源软件,是正经的商业发行版,所以技术支持、补丁更新这些都走官方渠道,别去开源社区找源码了,找不着。
好,咱们直接进入正题。
一、数据库与表空间:先搭台子再唱戏
1.1 创建数据库
在金仓里,一个实例可以挂多个数据库,彼此数据隔离,但用户和权限是实例级别的。创建数据库的语法跟标准 SQL 大体一致,但参数上有些金仓特有的细节需要注意。
-- 最简写法,默认走 template1 模板,编码 UTF8
CREATE DATABASE school_db;
-- 生产环境推荐的严谨写法
CREATE DATABASE school_db
WITH
OWNER = system -- 指定属主,权限管理更清晰
ENCODING = 'UTF8' -- 编码必须显式指定,避免中文乱码
LC_COLLATE = 'zh_CN.UTF8' -- 中文排序规则
LC_CTYPE = 'zh_CN.UTF8' -- 字符分类
TABLESPACE = sys_default -- 默认表空间
CONNECTION LIMIT = 200; -- 最大连接数,0 表示无限制
几个容易踩的坑:
第一,数据库名别带特殊字符,字母数字下划线组合最稳妥,长度控制在 63 个字符以内。第二,LC_COLLATE 和 LC_CTYPE 一旦设定就很难改,所以建库前务必确认业务 locale 需求。第三,普通用户得有 CREATE DATABASE 权限才能建库,一般咱们用 system 账号初始化。
想确认库建好了没,除了连上图形化管理工具看一眼,命令行里用 \l(小写 L)最方便,这是 ksql 客户端的元命令,后面会细讲。
1.2 表空间管理
表空间这概念 Oracle 用户应该很熟悉,简单说就是给数据文件在物理磁盘上找个落脚的地方。合理规划表空间,对大表分磁盘存储、IO 隔离都有好处。
-- 创建表空间,先确保操作系统目录存在且有权限
CREATE TABLESPACE academic_tbs
LOCATION '/data/kingbase/academic';
-- 给表空间改个名(生产环境谨慎操作)
ALTER TABLESPACE academic_tbs RENAME TO academic_data_tbs;
-- 查看所有表空间
\db
注意: 金仓里默认自带几个系统表空间,sys_default 是默认表空间,sys_global 放共享系统表,sysaudit 跟审计相关。自己建的表空间删之前必须保证里面没对象,否则 DROP TABLESPACE 会报错。
二、数据类型:别拿 MySQL 的经验硬套
从 MySQL 迁到金仓,数据类型是最容易翻车的地方。有些名字一样,实现不一样;有些金仓有,MySQL 没有。这里把最常用的几个捋清楚。
2.1 SERIAL:自增列的三种玩法
金仓里实现自增主要有三种方式,推荐度从高到低排:
方式一:SERIAL 伪类型(最简单)
CREATE TABLE test_serial (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
SERIAL 底层其实是 INTEGER 加了一个序列对象,并且自动绑定了默认值表达式。插入时如果不给 id 赋值,系统就自动从序列里取下一个值。这种方式建表最省事,但序列名是系统自动生成的,想手动重置得先查出序列名。
方式二:显式 SEQUENCE(最灵活)
CREATE SEQUENCE seq_student_id
INCREMENT BY 1
START WITH 1000
MINVALUE 1000
MAXVALUE 999999
CACHE 20
NOCYCLE;
CREATE TABLE students (
student_id INTEGER NOT NULL DEFAULT nextval('seq_student_id') PRIMARY KEY,
student_name VARCHAR(50) NOT NULL
);
这种方式的优势是序列可以被多张表共享,而且参数完全可控。CACHE 值建议根据并发量调,高并发场景可以适当加大,但别太大,实例异常重启会丢缓存号。
方式三:IDENTITY 列(SQL 标准语法)
CREATE TABLE test_identity (
id INTEGER GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY,
name VARCHAR(100)
);
GENERATED ALWAYS 意味着你不能显式往这列插值,除非用 OVERRIDING SYSTEM VALUE;如果业务偶尔需要手动指定 ID,改成 GENERATED BY DEFAULT AS IDENTITY 更合适。
2.2 VARCHAR vs TEXT
VARCHAR(n) 变长字符串,跟 MySQL 的 VARCHAR 行为类似,但金仓里 n 指的是字符数而不是字节数,这对中文很友好。如果存长文本,比如文章内容、日志原文,直接用 TEXT,没有长度限制,性能跟 VARCHAR 差别不大。
切记: 金仓里没有 VARCHAR(255) 这种 MySQL 惯性写法的长度崇拜,合适的业务长度就是最好的长度,别一上来就 255。
2.3 NUMERIC:钱相关的一定要用
存金额、费率这种对精度敏感的数据,一定用 NUMERIC(precision, scale),别用 FLOAT 或 DOUBLE。
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
amount NUMERIC(18, 2) NOT NULL, -- 总共18位,小数点后2位
discount NUMERIC(5, 4) -- 比如 0.8500 表示85折
);
NUMERIC 内部是变长存储,精确到小数点后几位完全可控,财务报表对账的时候不会出那种 19.9999998 的幺蛾子。
2.4 TIMESTAMP 家族
金仓里时间管理主要有三个类型:
DATE:只有日期,没有时分秒TIME:只有时分秒,没有日期TIMESTAMP [WITHOUT TIME ZONE]:日期加时间,不带时区信息TIMESTAMPTZ(或TIMESTAMP WITH TIME ZONE):带时区,存储时会自动转为 UTC,显示时再转回会话时区
如果你的业务跨时区,比如海外部署,建议用 TIMESTAMPTZ;纯国内业务,TIMESTAMP WITHOUT TIME ZONE 够用了,还少一层转换开销。
三、表操作:建表只是开始
3.1 创建表与约束
建表这件事,金仓的语法跟标准 SQL 很贴近,但有几个从 MySQL 迁过来的同事经常写错的点:
错误示范(MySQL 惯性):
-- 金仓里这么写会报错!
CREATE TABLE bad_example (
id INT(10), -- 错误:INT 不能指定显示宽度
age INT(3), -- 错误:同上
name VARCHAR(225) -- 语法能过,但没必要硬套 225
);
正确姿势:
CREATE TABLE departments (
dept_id SERIAL PRIMARY KEY,
dept_name VARCHAR(50) NOT NULL,
dept_code VARCHAR(20) UNIQUE, -- 唯一约束
budget NUMERIC(15, 2) CHECK (budget >= 0), -- 检查约束
established_date DATE DEFAULT CURRENT_DATE,
status SMALLINT DEFAULT 1 CHECK (status IN (0, 1)) -- 0停用 1启用
) TABLESPACE academic_data_tbs;
3.2 修改表结构(ALTER TABLE)
业务需求变了,加字段、改类型、删索引这些操作 inevitable。
-- 新增列
ALTER TABLE departments ADD COLUMN manager_id INTEGER;
-- 修改列类型(注意:已有数据必须兼容新类型)
ALTER TABLE departments ALTER COLUMN dept_name TYPE VARCHAR(100);
-- 加外键约束(假设有 employees 表)
ALTER TABLE departments
ADD CONSTRAINT fk_dept_manager
FOREIGN KEY (manager_id) REFERENCES employees(emp_id);
-- 删列(谨慎!生产环境先确认列上没依赖)
ALTER TABLE departments DROP COLUMN IF EXISTS temp_col;
-- 重命名表
ALTER TABLE departments RENAME TO dept_info;
外键约束的注意事项: 金仓对外键的支持很完整,级联删除、级联更新都能玩。但性能上,大表加外键会锁表,建议在业务低峰期操作,或者先建数据后加约束。
3.3 索引管理
索引是查询优化的基本功,但别乱建,写操作会受影响。
-- 普通 B-tree 索引,适用范围查询和等值查询
CREATE INDEX idx_dept_name ON departments(dept_name);
-- 多列联合索引,注意列顺序,最左前缀原则依然适用
CREATE INDEX idx_dept_code_status ON departments(dept_code, status);
-- 唯一索引,跟 UNIQUE 约束效果类似,但索引可以包含更多列
CREATE UNIQUE INDEX idx_dept_code_unique ON departments(dept_code);
-- 查看某张表的索引
\di departments
-- 删除索引
DROP INDEX IF EXISTS idx_dept_name;
金仓默认使用 B-tree 索引,大部分场景够用。如果涉及全文检索,可以考虑 GIN 索引,但那是进阶话题了,这篇先不展开。
四、数据操作:增删改查的日常
4.1 插入数据(INSERT)
单条插入比较简单,重点说说批量插入和从查询结果插入。
-- 单条插入,推荐显式指定列名,避免表结构变更后翻车
INSERT INTO departments (dept_name, dept_code, budget)
VALUES ('计算机学院', 'CS', 500000.00)
RETURNING dept_id; -- 返回刚插入的主键,业务层经常用
-- 批量插入,性能比单条循环好得多
INSERT INTO departments (dept_name, dept_code, budget, status)
VALUES
('数学学院', 'MATH', 300000.00, 1),
('物理学院', 'PHYS', 450000.00, 1),
('文学院', 'LIT', 200000.00, 1);
-- 从查询结果插入,常用于数据归档或同步
INSERT INTO dept_archive (dept_id, dept_name, archived_date)
SELECT dept_id, dept_name, CURRENT_TIMESTAMP
FROM departments
WHERE status = 0;
批量插入的性能提示: 如果一次性插入几万条以上,建议外面套个事务,或者直接用 COPY 命令(类似 PostgreSQL 的 COPY,金仓也支持),那才是真正的大杀器。
4.2 更新与删除(UPDATE / DELETE)
这两个操作一定要养成加 WHERE 的习惯,除非你真的想全表更新。
-- 更新,记得先 SELECT 确认影响范围
UPDATE departments
SET budget = budget * 1.1, status = 1
WHERE dept_code = 'CS';
-- 删除,同理
DELETE FROM departments
WHERE status = 0 AND established_date < '2020-01-01';
-- 如果想清空整表并重置序列,用 TRUNCATE 比 DELETE 快得多
TRUNCATE TABLE temp_logs RESTART IDENTITY;
TRUNCATE 是 DDL 操作,不记行级日志,所以飞快,但没法回滚(除非在事务里),而且会重置关联的序列计数器。
4.3 连接查询(JOIN)
JOIN 语法金仓跟标准 SQL 基本一致,这里用学生选课的场景把几种常用 JOIN 演示一遍。
假设有学生表 students 和班级表 classes:
-- INNER JOIN:只返回匹配的记录
SELECT s.student_name, c.class_name
FROM students s
INNER JOIN classes c ON s.class_id = c.class_id;
-- LEFT JOIN:返回左表所有记录,右表不匹配填 NULL
-- 查哪些学生还没分班
SELECT s.student_name, c.class_name
FROM students s
LEFT JOIN classes c ON s.class_id = c.class_id
WHERE c.class_id IS NULL;
-- RIGHT JOIN:反过来,返回右表所有记录
-- 查哪些班级暂时没学生
SELECT s.student_name, c.class_name
FROM students s
RIGHT JOIN classes c ON s.class_id = c.class_id
WHERE s.student_id IS NULL;
-- FULL OUTER JOIN:两边都保留,匹配不上的填 NULL
SELECT s.student_name, c.class_name
FROM students s
FULL OUTER JOIN classes c ON s.class_id = c.class_id;
写 JOIN 的小建议: 多表关联时,先把过滤条件写在 ON 子句里,聚合过滤放 WHERE 里,这样逻辑清晰,优化器也更容易生成好计划。
五、视图与序列:简化复杂逻辑
5.1 视图(VIEW)
视图就是个存起来的查询语句,主要作用是简化复杂 SQL 和做权限隔离。
-- 创建一个视图,展示各学院预算使用情况
CREATE VIEW v_dept_budget_summary AS
SELECT
d.dept_name,
d.budget,
COUNT(e.emp_id) AS employee_count,
d.budget / NULLIF(COUNT(e.emp_id), 0) AS budget_per_capita
FROM departments d
LEFT JOIN employees e ON d.dept_id = e.dept_id
GROUP BY d.dept_id, d.dept_name, d.budget;
-- 查询视图跟查表一样
SELECT * FROM v_dept_budget_summary WHERE budget_per_capita > 50000;
-- 查看视图定义
\d+ v_dept_budget_summary
金仓的视图默认不是物化的,每次查询都会执行底层 SQL。如果数据量大且查询频繁,可以考虑物化视图(CREATE MATERIALIZED VIEW),但需要手动或定时刷新。
5.2 序列(SEQUENCE)的运维
前面建表时提过序列,这里补充几个日常运维命令:
-- 查看当前序列值(不会推进序列)
SELECT currval('seq_student_id');
-- 查看下一个值(会推进序列!)
SELECT nextval('seq_student_id');
-- 设置序列当前值,常用于数据迁移后校准
SELECT setval('seq_student_id', 10000, false);
-- 第三个参数 false 表示下一个 nextval 返回 10001,true 则返回 10000
-- 查看数据库里有哪些序列
\ds
重要提醒: nextval 是发号器,调一次就少一个号,而且不随事务回滚而回滚。如果业务要求主键绝对连续(比如财务票据号),别用序列,自己实现锁表发号。
六、元数据查询与 ksql 运维命令
金仓的命令行工具叫 ksql,功能很强大,日常运维很多活儿不用写 SQL,敲个反斜线命令就搞定。
6.1 常用元命令速查
-- 连接数据库(命令行)
-- ksql -U system -d school_db -p 54321
-- 进入 ksql 后的常用命令
\l -- 列出所有数据库
\c school_db -- 切换到 school_db 数据库
\dt -- 列出当前模式下所有表
\dt *.* -- 列出所有模式下的所有表
\d departments -- 查看 departments 表结构(列、约束、索引)
\d+ departments -- 更详细,包含注释、存储参数
\di -- 列出所有索引
\di departments -- 列出某张表的索引
\ds -- 列出所有序列
\dv -- 列出所有视图
\dn -- 列出所有模式(schema)
\du -- 列出所有用户/角色
\db -- 列出所有表空间
\dp -- 列出权限分配情况
\timing on -- 开启 SQL 执行时间显示,调优必备
\x -- 切换横向/纵向显示结果,宽表时很有用
\q -- 退出 ksql
6.2 通过系统表查元数据
有时候需要在应用程序里动态获取数据库结构,这时候得查系统表。金仓的系统表命名以 sys_ 开头,跟 Oracle 的 dba_、MySQL 的 information_schema 是一个概念。
-- 查某张表的列信息
SELECT column_name, data_type, character_maximum_length, is_nullable
FROM information_schema.columns
WHERE table_name = 'departments';
-- 查当前数据库所有表及其属主
SELECT schemaname, tablename, tableowner
FROM sys_tables
WHERE schemaname NOT IN ('sys_catalog', 'information_schema');
-- 查表上的约束
SELECT conname, contype, pg_get_constraintdef(oid) AS definition
FROM sys_constraint
WHERE conrelid = 'departments'::regclass;
-- 查索引及对应表
SELECT schemaname, tablename, indexname, indexdef
FROM sys_indexes
WHERE tablename = 'departments';
开发 Tips: 写自动化脚本或者代码生成器时,多查 information_schema,这是 SQL 标准视图,跨数据库兼容性最好。但如果是做深度运维,还是 sys_* 系统表信息更全。
七、学生选课系统完整实操案例
光说不练假把式,下面搭一套完整的学生选课系统,从建表、插数据到复杂查询,把前面讲的语法串起来用一遍。
7.1 完整建表脚本
-- 先建库(如果还没建)
CREATE DATABASE school_db WITH ENCODING = 'UTF8' LC_COLLATE = 'zh_CN.UTF8';
-- 连接 school_db 后执行以下脚本
-- 1. 学院表
CREATE TABLE departments (
dept_id SERIAL PRIMARY KEY,
dept_name VARCHAR(50) NOT NULL,
dept_code VARCHAR(20) UNIQUE NOT NULL,
location VARCHAR(100),
budget NUMERIC(15, 2) CHECK (budget >= 0),
established_year INTEGER CHECK (established_year BETWEEN 1900 AND 2100),
status SMALLINT DEFAULT 1 CHECK (status IN (0, 1))
);
-- 2. 学生表
CREATE TABLE students (
student_id SERIAL PRIMARY KEY,
student_no VARCHAR(20) UNIQUE NOT NULL, -- 学号
student_name VARCHAR(50) NOT NULL,
gender CHAR(2) CHECK (gender IN ('男', '女')),
birth_date DATE,
dept_id INTEGER NOT NULL,
enrollment_date DATE DEFAULT CURRENT_DATE,
phone VARCHAR(20),
email VARCHAR(100),
status SMALLINT DEFAULT 1 CHECK (status IN (0, 1, 2)), -- 0退学 1在读 2毕业
CONSTRAINT fk_student_dept FOREIGN KEY (dept_id)
REFERENCES departments(dept_id) ON DELETE RESTRICT
);
-- 3. 课程表
CREATE TABLE courses (
course_id SERIAL PRIMARY KEY,
course_code VARCHAR(20) UNIQUE NOT NULL,
course_name VARCHAR(100) NOT NULL,
credit NUMERIC(3, 1) CHECK (credit > 0 AND credit <= 10),
class_hours INTEGER CHECK (class_hours > 0),
dept_id INTEGER,
teacher_name VARCHAR(50),
max_capacity INTEGER DEFAULT 50 CHECK (max_capacity > 0),
semester VARCHAR(20), -- 如 '2025-2026-1'
CONSTRAINT fk_course_dept FOREIGN KEY (dept_id)
REFERENCES departments(dept_id) ON DELETE SET NULL
);
-- 4. 选课记录表(核心关联表)
CREATE TABLE enrollments (
enrollment_id SERIAL PRIMARY KEY,
student_id INTEGER NOT NULL,
course_id INTEGER NOT NULL,
enrollment_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
score NUMERIC(5, 2) CHECK (score BETWEEN 0 AND 100),
grade_point NUMERIC(3, 2), -- 绩点,后续用触发器或应用层计算
status SMALLINT DEFAULT 1 CHECK (status IN (0, 1)), -- 0退选 1正常
CONSTRAINT fk_enroll_student FOREIGN KEY (student_id)
REFERENCES students(student_id) ON DELETE CASCADE,
CONSTRAINT fk_enroll_course FOREIGN KEY (course_id)
REFERENCES courses(course_id) ON DELETE CASCADE,
CONSTRAINT uk_student_course UNIQUE (student_id, course_id) -- 防止重复选课
);
-- 5. 给常用查询列加索引
CREATE INDEX idx_student_dept ON students(dept_id);
CREATE INDEX idx_student_status ON students(status);
CREATE INDEX idx_course_dept ON courses(dept_id);
CREATE INDEX idx_course_semester ON courses(semester);
CREATE INDEX idx_enroll_student ON enrollments(student_id);
CREATE INDEX idx_enroll_course ON enrollments(course_id);
CREATE INDEX idx_enroll_score ON enrollments(score) WHERE status = 1; -- 部分索引,只索引正常选课记录
7.2 插入测试数据
-- 插入学院数据
INSERT INTO departments (dept_name, dept_code, location, budget, established_year)
VALUES
('计算机学院', 'CS', '理工楼A座', 5000000.00, 1985),
('数学学院', 'MATH', '理学楼B座', 3200000.00, 1950),
('外国语学院', 'FL', '文科楼C座', 2800000.00, 1990),
('物理学院', 'PHYS', '理工楼D座', 4100000.00, 1960);
-- 插入学生数据
INSERT INTO students (student_no, student_name, gender, birth_date, dept_id, phone, email, status)
VALUES
('2024001', '张三', '男', '2002-05-15', 1, '13800138001', 'zhangsan@school.edu.cn', 1),
('2024002', '李四', '女', '2003-08-22', 1, '13900139002', 'lisi@school.edu.cn', 1),
('2024003', '王五', '男', '2001-12-01', 2, '13700137003', 'wangwu@school.edu.cn', 1),
('2024004', '赵六', '女', '2002-03-18', 3, '13600136004', 'zhaoliu@school.edu.cn', 1),
('2024005', '陈七', '男', '2003-11-30', 1, '13500135005', 'chenqi@school.edu.cn', 0),
('2024006', '刘八', '女', '2002-07-08', 4, '15000150006', 'liuba@school.edu.cn', 1);
-- 插入课程数据
INSERT INTO courses (course_code, course_name, credit, class_hours, dept_id, teacher_name, max_capacity, semester)
VALUES
('CS101', '数据库原理', 4.0, 64, 1, '张教授', 60, '2025-2026-1'),
('CS102', '操作系统', 3.5, 56, 1, '李教授', 55, '2025-2026-1'),
('MATH201', '高等数学', 5.0, 80, 2, '王教授', 100, '2025-2026-1'),
('FL101', '大学英语', 3.0, 48, 3, '赵教授', 80, '2025-2026-1'),
('PHYS301', '量子力学', 4.0, 64, 4, '陈教授', 40, '2025-2026-1');
-- 插入选课数据(含成绩)
INSERT INTO enrollments (student_id, course_id, score, status)
VALUES
(1, 1, 85.50, 1),
(1, 2, 78.00, 1),
(1, 3, 92.00, 1),
(2, 1, 91.00, 1),
(2, 4, 88.50, 1),
(3, 3, 76.50, 1),
(3, 5, 82.00, 1),
(4, 4, 95.00, 1),
(5, 1, 62.00, 0), -- 已退选
(6, 2, 88.00, 1),
(6, 5, 79.50, 1);
7.3 复杂查询实战
需求 1:查询每个学生的选课情况,包括学院、课程、成绩,并用 CASE WHEN 做成绩分级
SELECT
s.student_no,
s.student_name,
d.dept_name,
c.course_name,
c.credit,
e.score,
CASE
WHEN e.score >= 90 THEN '优秀'
WHEN e.score >= 80 THEN '良好'
WHEN e.score >= 70 THEN '中等'
WHEN e.score >= 60 THEN '及格'
ELSE '不及格'
END AS grade_level,
CASE
WHEN e.score >= 90 THEN 4.0
WHEN e.score >= 85 THEN 3.7
WHEN e.score >= 82 THEN 3.3
WHEN e.score >= 78 THEN 3.0
WHEN e.score >= 75 THEN 2.7
WHEN e.score >= 72 THEN 2.3
WHEN e.score >= 68 THEN 2.0
WHEN e.score >= 66 THEN 1.7
WHEN e.score >= 64 THEN 1.3
WHEN e.score >= 60 THEN 1.0
ELSE 0.0
END AS gpa_point
FROM students s
INNER JOIN departments d ON s.dept_id = d.dept_id
LEFT JOIN enrollments e ON s.student_id = e.student_id AND e.status = 1
LEFT JOIN courses c ON e.course_id = c.course_id
ORDER BY s.student_no, c.course_code;
需求 2:统计各学院学生的平均成绩、最高成绩、挂科人数
SELECT
d.dept_name,
COUNT(DISTINCT s.student_id) AS total_students,
ROUND(AVG(e.score), 2) AS avg_score,
MAX(e.score) AS max_score,
MIN(e.score) AS min_score,
COUNT(CASE WHEN e.score < 60 AND e.status = 1 THEN 1 END) AS fail_count
FROM departments d
LEFT JOIN students s ON d.dept_id = s.dept_id AND s.status = 1
LEFT JOIN enrollments e ON s.student_id = e.student_id AND e.status = 1
GROUP BY d.dept_id, d.dept_name
ORDER BY avg_score DESC NULLS LAST;
需求 3:查询每门课程的选课人数、平均分,并筛选出选课人数超过 2 人的课程
SELECT
c.course_code,
c.course_name,
c.teacher_name,
c.max_capacity,
COUNT(e.student_id) AS enrolled_count,
ROUND(AVG(e.score), 2) AS avg_score,
c.max_capacity - COUNT(e.student_id) AS remaining_seats
FROM courses c
LEFT JOIN enrollments e ON c.course_id = e.course_id AND e.status = 1
WHERE c.semester = '2025-2026-1'
GROUP BY c.course_id, c.course_code, c.course_name, c.teacher_name, c.max_capacity
HAVING COUNT(e.student_id) > 2
ORDER BY enrolled_count DESC;
需求 4:找出没有选过任何课程的学生( EXISTS / NOT EXISTS 用法)
SELECT
s.student_no,
s.student_name,
d.dept_name
FROM students s
INNER JOIN departments d ON s.dept_id = d.dept_id
WHERE s.status = 1
AND NOT EXISTS (
SELECT 1
FROM enrollments e
WHERE e.student_id = s.student_id AND e.status = 1
);
需求 5:查询每名学生已修学分和绩点(GPA)
SELECT
s.student_no,
s.student_name,
SUM(CASE WHEN e.score >= 60 THEN c.credit ELSE 0 END) AS total_credits,
ROUND(
SUM(
CASE
WHEN e.score >= 90 THEN 4.0 * c.credit
WHEN e.score >= 85 THEN 3.7 * c.credit
WHEN e.score >= 82 THEN 3.3 * c.credit
WHEN e.score >= 78 THEN 3.0 * c.credit
WHEN e.score >= 75 THEN 2.7 * c.credit
WHEN e.score >= 72 THEN 2.3 * c.credit
WHEN e.score >= 68 THEN 2.0 * c.credit
WHEN e.score >= 66 THEN 1.7 * c.credit
WHEN e.score >= 64 THEN 1.3 * c.credit
WHEN e.score >= 60 THEN 1.0 * c.credit
ELSE 0.0
END
) / NULLIF(SUM(CASE WHEN e.score >= 60 THEN c.credit END), 0),
2
) AS gpa
FROM students s
LEFT JOIN enrollments e ON s.student_id = e.student_id AND e.status = 1
LEFT JOIN courses c ON e.course_id = c.course_id
WHERE s.status = 1
GROUP BY s.student_id, s.student_no, s.student_name
ORDER BY gpa DESC NULLS LAST;
7.4 视图封装常用查询
-- 封装学生成绩单视图
CREATE OR REPLACE VIEW v_student_transcript AS
SELECT
s.student_no,
s.student_name,
d.dept_name,
c.course_code,
c.course_name,
c.credit,
c.semester,
e.score,
CASE
WHEN e.score >= 90 THEN '优秀'
WHEN e.score >= 80 THEN '良好'
WHEN e.score >= 70 THEN '中等'
WHEN e.score >= 60 THEN '及格'
ELSE '不及格'
END AS grade_level
FROM students s
JOIN departments d ON s.dept_id = d.dept_id
JOIN enrollments e ON s.student_id = e.student_id
JOIN courses c ON e.course_id = c.course_id
WHERE s.status = 1 AND e.status = 1;
-- 查询视图
SELECT * FROM v_student_transcript WHERE student_no = '2024001';
八、日常运维避坑指南
8.1 字符集与乱码
金仓默认 UTF8,但客户端编码如果不一致,中文显示会变问号。连上 ksql 后先执行:
SHOW client_encoding;
-- 如果不匹配,手动设置
SET client_encoding TO 'UTF8';
8.2 大小写敏感性
金仓里对象名默认转小写,如果你用双引号建了 "Student" 这种大写表名,以后查询必须带双引号,否则找不到。建议统一小写加下划线,跟 MySQL 的习惯保持一致。
8.3 事务与锁
金仓支持完整的事务 ACID,默认隔离级别是 READ COMMITTED。批量操作时务必显式开启事务:
BEGIN;
-- 大量 DML 操作
COMMIT;
-- 或者 ROLLBACK;
长事务会占用锁和 WAL 日志空间,尽量避免在业务高峰期跑大批量更新。
8.4 序列重置
迁移数据后,自增主键可能会跟序列当前值对不上,导致主键冲突。校准脚本:
SELECT SETVAL('students_student_id_seq',
(SELECT MAX(student_id) FROM students),
true);
注意 sys_get_serial_sequence 可以帮你查到 SERIAL 列对应的序列名。
8.5 备份与恢复
虽然这篇主要讲 SQL,但运维不得不提一嘴。金仓提供 sys_dump 做逻辑备份,sys_basebackup 做物理备份。日常建议逻辑备份加物理备份双保险:
# 逻辑备份单库
sys_dump -U system -d school_db -f school_db_backup.sql
# 恢复
ksql -U system -d school_db -f school_db_backup.sql
结语
写到这里,基本上把金仓数据库日常开发和运维中最常用的 SQL 语法、ksql 命令、以及一个完整的业务案例都过了一遍。从 MySQL 或 Oracle 迁移过来的同学,最大的挑战其实不是语法差异,而是思维转换——比如别再想着 INT(10) 这种显示宽度,别再拿 FLOAT 存金额,还有就是要习惯 ksql 里那些反斜线命令。
金仓作为国产商业数据库,生态工具链在不断完善,官方文档和论坛是遇到问题时的第一手资源。建议手头常备一份《KingbaseES SQL 参考手册》,比在网上搜碎片信息靠谱得多。
希望这篇手册能帮你在实际项目中少踩几个坑,多写几行稳健的 SQL。如果有具体的迁移或调优场景,欢迎结合实际业务继续深挖,数据库这玩意儿,光看不练永远是纸上谈兵。
附录:快速参考卡
| 操作 | 命令/语法 |
|---|---|
| 连接数据库 | ksql -U 用户名 -d 库名 -p 端口 |
| 列出所有库 | \l |
| 列出所有表 | \dt |
| 查看表结构 | \d 表名 |
| 查看索引 | \di |
| 查看视图 | \dv |
| 查看用户 | \du |
| 查看表空间 | \db |
| 开启执行计时 | \timing on |
| 创建数据库 | CREATE DATABASE ... WITH ENCODING='UTF8' |
| 创建表空间 | CREATE TABLESPACE ... LOCATION '路径' |
| 创建表 | CREATE TABLE ... (列名 类型 约束) |
| 修改表 | ALTER TABLE ... ADD/DROP/ALTER COLUMN ... |
| 创建索引 | CREATE INDEX ... ON 表(列) |
| 插入数据 | INSERT INTO ... VALUES ... |
| 批量插入 | INSERT INTO ... VALUES (...), (...), ... |
| 更新数据 | UPDATE ... SET ... WHERE ... |
| 删除数据 | DELETE FROM ... WHERE ... |
| 清空表 | TRUNCATE TABLE ... RESTART IDENTITY |
| 创建视图 | CREATE VIEW ... AS SELECT ... |
| 创建序列 | CREATE SEQUENCE ... START WITH ... INCREMENT BY ... |
| 获取序列值 | nextval('序列名') / currval('序列名') |