第2章 SQL 基础(MySQL 对照速成)
🎯 本章学习目标
- 会在达梦里创建用户/模式,并理解「登录用户 → 同名模式 → 对象归属」这条主线。
- 记住 MySQL → 达梦数据类型对照表,知道哪些类型要改写、哪些直接兼容。
- 用达梦语法完成建表(DDL)、增删改(DML)、查询(DQL)全流程,每步都能和 MySQL 写法互相翻译。
- 掌握 MySQL 的
AUTO_INCREMENT在达梦中的两种替代:IDENTITY 自增列与序列 SEQUENCE。 - 掌握达梦三种分页写法(LIMIT / ROWNUM / OFFSET-FETCH),知道 MyBatis 里该用哪种。
- 建立「迁移十大坑」清单,能解释每个坑的成因。
本章所有示例默认在达梦原生模式(
COMPATIBLE_MODE=0)下执行。MySQL 兼容模式下部分写法会「睁一只眼闭一只眼」放行,第 6 章专门讲。
2.1 用户、模式与权限:先把「我的东西放哪」搞清楚
MySQL 里你习惯 CREATE DATABASE shop; 然后 USE shop;。达梦里没有 freely 建库一说(库在建实例时已定),对应动作是建用户,自动得到同名模式:
-- 达梦:创建业务用户(会自动创建同名模式 SHOP)
CREATE USER SHOP IDENTIFIED BY "Shop_Pass123";
GRANT RESOURCE TO SHOP; -- 拥有在本模式下建表等对象权限
GRANT CONNECT TO SHOP; -- 拥有登录会话的基础权限-- 对照 MySQL
CREATE DATABASE shop;
CREATE USER 'shop'@'%' IDENTIFIED BY 'xxx';
GRANT ALL PRIVILEGES ON shop.* TO 'shop'@'%';
FLUSH PRIVILEGES;之后以 SHOP 用户登录,CREATE TABLE T_ORDER ... 的表就落在模式 SHOP 下;SYSDBA 想看它,要写 SHOP.T_ORDER(模式名.表名)。这层「模式」概念正是 MySQL 同学最容易懵的地方,记住对应关系即可:
| MySQL 心智 | 达梦心智 |
|---|---|
| 库名 shop | 模式名 SHOP(由同名用户承载) |
shop.t_order 跨库引用 | SHOP.T_ORDER 跨模式引用 |
GRANT ON shop.* | GRANT 对象权限 ON SHOP.T_ORDER 或角色打包 |
常见达梦角色
PUBLIC(人人都有,查系统视图等基础权限)、RESOURCE(建对象)、DBA(管理员,等价 MySQL 的 ALL PRIVILEGES WITH GRANT OPTION)。学习阶段用 SYSDBA 就够了。
2.2 数据类型对照表
| MySQL | 达梦 | 说明 |
|---|---|---|
TINYINT / SMALLINT / INT / BIGINT | ✅ 同名支持 | 达梦 INT 是 4 字节;TINYINT 在达梦是 -128~127(MySQL 是无符号 0~255,布尔标记用 TINYINT 时注意符号) |
DECIMAL(m,n) / NUMERIC | ✅ 同名,另有 NUMBER(p[,s]) | NUMBER 是 Oracle 味的万能精确数 |
FLOAT / DOUBLE | ✅ 同名 | 行为基本一致 |
CHAR(n) / VARCHAR(n) | ✅ 同名,另有 VARCHAR2(n) | VARCHAR2 与 VARCHAR 等价(Oracle 兼容别名);长度按字符计,UTF-8 中文不会像 MySQL 那样按字节膨胀一半,但超长一样报错 |
TEXT / MEDIUMTEXT / LONGTEXT | TEXT / CLOB | 达梦用 CLOB 承接大文本;TEXT 可当别名 |
BLOB / LONGBLOB | BLOB | 大二进制,JDBC 读写方式见第 5 章 CLOB/BLOB 注意项 |
DATE(仅日期) | ⚠️ 达梦 DATE 含时分秒(Oracle 风格) | 想要纯日期用 DATE 只管存取不管类型?错——达梦 DATE 精确到秒。严格「仅日期」建议 CHAR(8)/DATE 取舍或直接用 TIMESTAMP 自己截断 |
DATETIME | TIMESTAMP(微秒)/ TIMESTAMP(3)(毫秒) | Java 的 java.util.Date/LocalDateTime 都映射顺畅 |
TIME | TIME | 支持 |
YEAR | ❌ 无 | 用 SMALLINT 替代 |
ENUM / SET | ❌ 无 | 用 VARCHAR + CHECK 或字典表 |
JSON | DM8 支持 JSON 类型与函数(版本相关) | 低版本用 CLOB 存字符串 |
BIT(1) / BOOLEAN | BIT(0/1) | 达梦布尔语义列习惯用 BIT/NUMBER(1) |
达梦 DATE 含时间,是迁移里最隐蔽的坑之一
MySQL 里 DATE 和 DATETIME 泾渭分明;达梦 DATE 实际存储到秒。从达梦把 DATE 列映射成 Java LocalDate 时,时分秒可能被悄悄截掉或比较不等。团队约定:时间一律 TIMESTAMP,纯日期一律 DATE 并在应用层截断,可以躲开所有纠缠。
2.3 DDL:建表 / 改表 / 删表
2.3.1 建表对照
-- MySQL 原写法(回忆用)
CREATE TABLE t_user (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL COMMENT '用户名',
status TINYINT NOT NULL DEFAULT 0 COMMENT '状态 0禁用 1启用',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';-- 达梦等价写法
CREATE TABLE T_USER (
ID BIGINT IDENTITY(1, 1) PRIMARY KEY, -- 自增:IDENTITY(种子, 增量)
USERNAME VARCHAR(50) NOT NULL,
STATUS SMALLINT DEFAULT 0 NOT NULL,
CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT UK_USERNAME UNIQUE (USERNAME)
);
COMMENT ON TABLE T_USER IS '用户表'; -- 注释是独立语句
COMMENT ON COLUMN T_USER.USERNAME IS '用户名';
COMMENT ON COLUMN T_USER.STATUS IS '状态 0禁用 1启用';差异逐条点名:
- 没有 ENGINE / CHARSET 子句:引擎唯一,字符集是建库级参数(第 1 章)。
- COMMENT 不内联:MySQL 的列内
COMMENT '...'达梦不认,要用COMMENT ON。 - 自增:
AUTO_INCREMENT→IDENTITY(种子,增量),或改用序列(2.3.3 更推荐)。 - UNSIGNED 不存在:MySQL
BIGINT UNSIGNED直接写BIGINT,注意上限差异。 - 约束显式命名:好习惯是
CONSTRAINT 名字 UNIQUE/CHECK/FOREIGN KEY,迁移后靠DBA_CONSTRAINTS排查。
2.3.2 改表 / 删表:几乎无缝
ALTER TABLE T_USER ADD COLUMN PHONE VARCHAR(20); -- MySQL: ADD COLUMN 一样可写
ALTER TABLE T_USER MODIFY COLUMN USERNAME VARCHAR(100) NOT NULL; -- ✅ 达梦支持 MODIFY(也支持 Oracle 的 ALTER COLUMN)
ALTER TABLE T_USER DROP COLUMN PHONE;
RENAME T_USER TO T_ACCOUNT; -- MySQL: RENAME TABLE a TO b
DROP TABLE T_ACCOUNT PURGE; -- 达梦默认删除进「回收站」,PURGE 彻底删
TRUNCATE TABLE T_USER; -- 与 MySQL 同回收站
DM8 里 DROP TABLE 默认把表放进回收站(参数 PURGE_AFTER_DROP_TABLE 控制),可 FLASHBACK TABLE 找回——MySQL 没有这个能力,运维时反而要记得:清空空间用 PURGE RECYCLEBIN;。学习阶段觉得意外就把它关掉或习惯 PURGE。
2.3.3 自增主键的达梦正道:序列
IDENTITY 简单,但跨表统一发号、预取号、和 MyBatis 配合这些场景下,序列才是达梦主流方案(对应 Oracle 用法,MySQL 8 之前没有的对象):
CREATE SEQUENCE SEQ_T_USER
START WITH 1
INCREMENT BY 1
CACHE 20; -- 高速缓存,MySQL 无此概念,性能高
INSERT INTO T_USER (ID, USERNAME) VALUES (SEQ_T_USER.NEXTVAL, 'tom');
SELECT SEQ_T_USER.CURRVAL FROM DUAL; -- 当前值(本会话内)
SELECT SEQ_T_USER.NEXTVAL FROM DUAL; -- 取下一个| 方案 | MySQL | 达梦 | 选型建议 |
|---|---|---|---|
| 列自增 | AUTO_INCREMENT | IDENTITY(1,1) | 小表、单表主键够用 |
| 独立发号对象 | ❌ 无原生 | SEQUENCE | 高并发、多表共用号段、需要在 INSERT 前就拿 id 的业务 |
| 应用发号 | 雪花算法 | 雪花算法不变 | 分库分表项目迁移期维持原状最稳 |
2.4 DML:增删改
-- 单行插入:完全同 MySQL
INSERT INTO T_USER (USERNAME, STATUS) VALUES ('amy', 1);
INSERT INTO T_USER VALUES ('bo', 0, SYSDATE); -- 全列可省列名(不推荐)
-- 多行插入:MySQL 的 VALUES,(...) 语法达梦同样支持
INSERT INTO T_USER (USERNAME) VALUES ('c'), ('d');
-- 达梦/Oracle 味的批量写法
INSERT ALL
INTO T_USER (USERNAME) VALUES ('e')
INTO T_USER (USERNAME) VALUES ('f')
SELECT 1 FROM DUAL;
-- 插 NULL:达梦严格区分 '' 与 NULL。MySQL 里 NOT NULL 列插 '' 能混过去,
-- 达梦默认 CASE_SENSITIVE 库中 '' 就是 NULL(Oracle 语义),VARCHAR 空串判断要写 IS NULL
UPDATE T_USER SET STATUS = 1, CREATE_TIME = SYSDATE WHERE USERNAME = 'amy';
DELETE FROM T_USER WHERE ID = 3;
-- MySQL 特色更新语法达梦不支持:
-- UPDATE t SET a=a+1 ORDER BY id LIMIT 1; ❌ 达梦无 UPDATE ... ORDER BY ... LIMIT
-- 改写成子查询定位:
UPDATE T_USER SET STATUS = STATUS + 1
WHERE ID IN (SELECT ID FROM T_USER WHERE STATUS = 0 AND ROWNUM <= 1);空字符串与 NULL
达梦默认把 '' 视作 NULL(与 Oracle 一致)。WHERE USERNAME = '' 永远查不到,要写 IS NULL。MySQL 迁移过来的项目若大量依赖 = '' 判空,要么改 SQL,要么开 MySQL 兼容模式(COMPATIBLE_MODE=4 下空串不再等于 NULL),这是行为级差异,比语法错误更危险,第 6 章的迁移检查清单第一条就是它。
2.5 DQL:查询与函数
2.5.1 分页(必考)
MySQL 一招 LIMIT 10 OFFSET 20 打天下;达梦给了三条路:
-- ① MySQL 味:LIMIT(达梦原生就支持,迁移成本最低)
SELECT * FROM T_USER ORDER BY ID LIMIT 10 OFFSET 20; -- 第 3 页,每页 10 条
SELECT * FROM T_USER ORDER BY ID LIMIT 20, 10; -- 同样效果:LIMIT 偏移, 行数
-- ② Oracle 味:ROWNUM 伪列(老项目/老手册常见)
SELECT * FROM (
SELECT A.*, ROWNUM RN FROM (SELECT * FROM T_USER ORDER BY ID) A WHERE ROWNUM <= 30
) WHERE RN > 20;
-- ③ SQL 标准:OFFSET-FETCH(DM8 支持,跨库可移植性最好)
SELECT * FROM T_USER ORDER BY ID OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;结论:日常开发直接用 ① LIMIT,写法和 MySQL 一模一样,MyBatis-Plus 的 DM 方言内部也是帮你拼 LIMIT 类语句;② 用于看懂老脚本;③ 用于了解标准。
2.5.2 常用函数对照表
| 场景 | MySQL | 达梦 | 备注 |
|---|---|---|---|
| 当前时间 | NOW() | SYSDATE / NOW() / CURRENT_TIMESTAMP | 达梦也认 NOW(),推荐 SYSDATE |
| 空值兜底 | IFNULL(x, 0) | NVL(x, 0) / COALESCE | MySQL 兼容模式下 IFNULL 可用 |
| 条件表达式 | CASE WHEN | CASE WHEN / DECODE | DECODE 是 Oracle 味简写 |
| 字符串拼接 | CONCAT(a, b) | a || b / CONCAT(a, b) | 竖线双引号写法最通用 |
| 日期→字符串 | DATE_FORMAT(t, '%Y-%m-%d') | TO_CHAR(t, 'YYYY-MM-DD') | 格式符完全不同,重点记 |
| 字符串→日期 | STR_TO_DATE | TO_DATE('2026-01-01','YYYY-MM-DD') | 返回 DATE(含时间) |
| 日期差 | DATEDIFF(d1, d2) 天数 | D1 - D2 得天数 / DATEDIFF(DD, 开始, 结束) | ⚠️ 达梦 DATEDIFF 是 SQLServer 风格:单位在前、开始在前,与 MySQL 参数顺序相反 |
| 日期加减 | DATE_ADD(t, INTERVAL 7 DAY) | t + 7(天)/ ADD_DAYS(t, 7) 函数 | 加月用 ADD_MONTHS |
| 截取字符串 | SUBSTRING(s, 1, 3) | SUBSTR(s, 1, 3) | 都支持,下标都从 1 开始 |
| 类型转换 | CAST | CAST / TO_NUMBER / TO_CHAR | |
| 找不匹配 | REGEXP | REGEXP_LIKE(col, '^[0-9]+$') | |
| 随机 | RAND() | RAND() / DBMS_RANDOM |
TO_CHAR 日期格式符速记(迁移重灾区,MySQL 的 % 风格在达梦统统换成单词):
| MySQL | 达梦 | 含义 |
|---|---|---|
%Y | YYYY | 四位年 |
%m | MM | 两位月 |
%d | DD | 两位日 |
%H:%i:%s | HH24:MI:SS | 24 小时制时分秒 |
%s(秒) | SS | MySQL 的小写 s 在达梦是「世纪」缩写位置,千万别照抄 |
2.5.3 分组、连接、子查询:语法一致,两处习惯差异
-- JOIN / 子查询 / GROUP BY 与 MySQL 完全同语法
SELECT U.STATUS, COUNT(*) CNT
FROM T_USER U JOIN T_ORDER O ON O.USER_ID = U.ID
WHERE U.CREATE_TIME >= DATE'2026-01-01' -- ANSI 日期字面量,达梦支持
GROUP BY U.STATUS
HAVING COUNT(*) > 10
ORDER BY CNT DESC;差异点只有两个:
- ONLY_FULL_GROUP_BY 更严格:MySQL 5.7+ 默认也开了严格模式,但不少团队环境关了;达梦没有「关掉」一说,SELECT 列表里的非聚合列必须全出现在 GROUP BY 中。
- 窗口函数完整支持:
ROW_NUMBER() OVER(PARTITION BY ... ORDER BY ...)与 MySQL 8.0 一致,达梦 8 全量可用,第 3 章实战。
2.5.4 UNION / 伪表 DUAL
SELECT 'A' X FROM DUAL UNION ALL SELECT 'B' X FROM DUAL; -- DUAL 照用
SELECT 1; -- 不写 FROM 也支持(MySQL 习惯可保留)注意:UNION 结果集列名以第一个 SELECT 为准,NULL 参与 UNION 时列类型推断比 MySQL 严格,必要时在第一段就 CAST 定型。
2.6 双引号、反引号与注释:语法糖差异清单
| 项 | MySQL | 达梦 |
|---|---|---|
| 引用标识符 | 反引号 `order` | 双引号 "ORDER"(没有反引号,兼容模式除外) |
| 字符串 | 单引号或双引号均可 "abc" | 只认单引号 'abc';双引号是标识符 |
| 行注释 | -- / # | --(# 不认) |
| 块注释 | /* */ | /* */ ✅ |
| 保留字 | 较多 | 较少但不同:如 VALUE, CHECK 用法有细微差异,字段起名先查 V$RESERVED_WORDS |
迁移工具建出来的「小写表名」陷阱链
DTS 从 MySQL 迁表时若保留了小写表名(带引号建表 "t_user"),而 Java 代码里写 FROM t_user(自动大写 → 找 T_USER),就会出现「对象树里明明有表,SQL 却报表不存在」。解法三选一:① 建库 CASE_SENSITIVE=N;② 迁移时统一转大写;③ 代码统一带引号(最不推荐)。第 5、6 章会再次遇到它。
2.7 迁移十大坑速查表(本章精华)
| # | 坑 | MySQL 习惯 | 达梦现实 | 一句话对策 |
|---|---|---|---|---|
| 1 | 表名大小写 | 大小写混用无所谓 | 未加引号一律大写存储 | 全链路统一「不加引号」 |
| 2 | 空串 = NULL | '' 与 NULL 两回事 | '' 即 NULL | 判空一律 IS NULL |
| 3 | 分页 | 只会 LIMIT | LIMIT 可用,但老脚本多是 ROWNUM | 看得懂三种写法 |
| 4 | 自增 | AUTO_INCREMENT | IDENTITY / SEQUENCE | 项目统一用序列 |
| 5 | 反引号 | ` 包裹字段 | 改双引号 " | 写 ANSI 风格 SQL |
| 6 | 日期格式符 | %Y-%m-%d | YYYY-MM-DD | 背 2.5.2 表 |
| 7 | DATE 含时间 | DATE 仅日期 | DATE 精确到秒 | 时间统一 TIMESTAMP |
| 8 | UPDATE...LIMIT | 常用 | 不支持 | 子查询 + ROWNUM 改写 |
| 9 | GROUP BY | 可宽松 | 严格全匹配 | 非聚合列全进 GROUP BY |
| 10 | 注释 | 列内 COMMENT '' | COMMENT ON 独立语句 | DDL 脚本二次加工 |
2.8 本章小结 + 练习
- 用户即模式,对象全名 =
模式.表;角色 CONNECT/RESOURCE 管基础权限。 - 类型与函数按 2.2、2.5.2 两张对照表「翻译」即可覆盖 90% 日常 SQL。
- 主键方案:IDENTITY 求快、SEQUENCE 求活;分页方案:LIMIT 求稳。
- 十条坑里最要命的是空串=NULL 与大小写,它们不是语法错误,是行为改变。
✍️ 课后练习
- 把你在 MySQL 项目里最熟的一张表(含注释、唯一键、自增主键)改写成达梦 DDL,并解释每一处改动。
- 创建序列
SEQ_T_ORDER,用它插入 5 行订单,分别用三种分页写法查「第 2 页 3 条」,比对结果。 - 写一句 SQL:查询注册时间是「2026 年 3 月」的用户,分别用 MySQL 思维和达梦思维写,再互查语法错误。
- (思考题)
INSERT INTO T_USER(USERNAME) VALUES('')后,SELECT COUNT(*) FROM T_USER WHERE USERNAME=''的结果是多少?为什么?
📖 参考答案
第 1 题:把 MySQL 表 DDL 改写成达梦版
拿一张典型的 MySQL 表开刀,第 1 步列出输入:
-- MySQL 原版
CREATE TABLE `t_product` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` VARCHAR(100) NOT NULL COMMENT '商品名',
`price` DECIMAL(10,2) DEFAULT 0.00 COMMENT '售价',
`status` TINYINT DEFAULT 1 COMMENT '1上架 0下架',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';第 2 步,逐处改写(每处都对应本章一个知识点):
-- 达梦版
CREATE TABLE T_PRODUCT (
ID BIGINT IDENTITY(1,1) PRIMARY KEY,
NAME VARCHAR(100) NOT NULL,
PRICE DECIMAL(10,2) DEFAULT 0.00,
STATUS SMALLINT DEFAULT 1,
CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT UK_NAME UNIQUE (NAME) -- ②③④⑥⑩ 全在里面
);
COMMENT ON TABLE T_PRODUCT IS '商品表';
COMMENT ON COLUMN T_PRODUCT.NAME IS '商品名';第 3 步,逐处解释改动(这就是「解释每一处」的答案本体):
| # | 改动 | 原因 |
|---|---|---|
| ① | 删掉所有反引号 | 达梦没有反引号;不加引号自动大写,风格统一 |
| ② | UNSIGNED 去掉 | 达梦无无符号类型,BIGINT 范围足够兼容 |
| ③ | AUTO_INCREMENT → IDENTITY(1,1) | 达梦自增列语法(项目统一用序列的另说,见 2.3) |
| ④ | TINYINT → SMALLINT | 达梦无 TINYINT(最小整型是 SMALLINT) |
| ⑤ | DATETIME → TIMESTAMP | 类型对照表;另外达梦的 DATE 也含时分秒,别用 DATE 存时间 |
| ⑥ | 内联 COMMENT '...' 全部搬出 | 达梦注释只能 COMMENT ON TABLE/COLUMN 独立语句 |
| ⑦ | ENGINE/CHARSET 删除 | 表存储引擎不存在;字符集建库时已定 |
| ⑧ | 表选项 UNIQUE KEY → 列约束/独立 CONSTRAINT | 达梦建表语法不接受 MySQL 表选项式索引写法 |
第 4 步(验证闭环),建完立刻能用三句话验:
INSERT INTO T_PRODUCT (NAME, PRICE) VALUES ('Java实战', 99.00); -- 不写 ID,IDENTITY 自动给 1
SELECT @@IDENTITY FROM DUAL; -- 1,本会话最后一次的自增值(兼容 SQLServer 写法);严谨场合直接查 SCOPE_IDENTITY()
SELECT COUNT(*) FROM T_PRODUCT WHERE NAME='Java实战'; -- 1,唯一键可命中第 2 题:序列插 5 行 + 三种分页写法查「第 2 页 3 条」
第 1 步,建表建序列并灌 7 行(7 行才能显出「第 2 页 3 条」的裁剪效果):
CREATE TABLE T_ORDER2 (ID BIGINT PRIMARY KEY, AMOUNT DECIMAL(10,2));
CREATE SEQUENCE SEQ_T_ORDER2 START WITH 1 INCREMENT BY 1;
INSERT INTO T_ORDER2 VALUES (SEQ_T_ORDER2.NEXTVAL, 10.00);
INSERT INTO T_ORDER2 VALUES (SEQ_T_ORDER2.NEXTVAL, 20.00);
INSERT INTO T_ORDER2 VALUES (SEQ_T_ORDER2.NEXTVAL, 30.00);
INSERT INTO T_ORDER2 VALUES (SEQ_T_ORDER2.NEXTVAL, 40.00);
INSERT INTO T_ORDER2 VALUES (SEQ_T_ORDER2.NEXTVAL, 50.00);
INSERT INTO T_ORDER2 VALUES (SEQ_T_ORDER2.NEXTVAL, 60.00);
INSERT INTO T_ORDER2 VALUES (SEQ_T_ORDER2.NEXTVAL, 70.00);
COMMIT;
SELECT * FROM T_ORDER2 ORDER BY ID; -- 输入对账底稿:ID 1~7,金额 10~70第 2 步,三种写法各跑一遍,结果必须完全一致(ID 4、5、6):
-- 写法① LIMIT(MySQL 手感,达梦直接支持)
SELECT ID, AMOUNT FROM T_ORDER2 ORDER BY ID LIMIT 3 OFFSET 3;
-- 写法② ROWNUM 经典两层(Oracle 老项目里最常见的形态)
SELECT * FROM (
SELECT A.*, ROWNUM RN FROM (
SELECT ID, AMOUNT FROM T_ORDER2 ORDER BY ID
) A WHERE ROWNUM <= 6 -- 6 = offset+limit = 3+3
) WHERE RN > 3; -- 跳过前 3 行
-- 写法③ OFFSET..FETCH(SQL 标准写法,未来移植性最好)
SELECT ID, AMOUNT FROM T_ORDER2 ORDER BY ID
OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY;三种写法输出均为:
ID AMOUNT
----------- ----------
4 40.00
5 50.00
6 60.00第 3 步,心法:写法②的 ROWNUM 必须在包了一层的子查询里且内层带 ORDER BY,直接 WHERE ROWNUM<=6 套在排序前会得到「先取随机 6 行再排序」的错误结果——这个坑在 MySQL 没有(没有 ROWNUM),迁移老 Oracle 脚本时最容易带进来。
第 3 题:查「2026 年 3 月注册」的两种写法互查
第 1 步,播种输入(复用第 2 题风格,新建小表避免碰主教程的 T_USER):
CREATE TABLE T_REG (ID INT PRIMARY KEY, REG_TIME TIMESTAMP);
INSERT INTO T_REG VALUES (1, TIMESTAMP'2026-02-28 23:59:59'); -- 边界:2月最后一秒
INSERT INTO T_REG VALUES (2, TIMESTAMP'2026-03-01 00:00:00'); -- 边界:3月第一秒
INSERT INTO T_REG VALUES (3, TIMESTAMP'2026-03-15 12:00:00');
INSERT INTO T_REG VALUES (4, TIMESTAMP'2026-04-01 00:00:00'); -- 边界:4月第一秒
COMMIT;第 2 步,MySQL 思维写法(函数包裹列)在达梦里能跑但错:
-- MySQL 习惯:DATE_FORMAT(t,'%Y-%m')='2026-03'
SELECT DATE_FORMAT(REG_TIME, '%Y-%m') FROM T_REG; -- 达梦不报错(部分版本),但 '%Y-%m' 不会按 MySQL 解释!达梦的 TO_CHAR 用 Oracle 格式符,% 风格直搬要么报错要么输出字面量——正确写法是用达梦思维:
-- 达梦写法①(推荐,索引可用):区间而非函数
SELECT ID FROM T_REG
WHERE REG_TIME >= DATE'2026-03-01' AND REG_TIME < DATE'2026-04-01'
ORDER BY ID; -- → 2, 3
-- 达梦写法②(TO_CHAR 风格匹配,全表扫描,只当备选)
SELECT ID FROM T_REG WHERE TO_CHAR(REG_TIME, 'YYYY-MM') = '2026-03' ORDER BY ID; -- → 2, 3第 3 步,输出对账:两种达梦写法都返回 ID 2、3;ID 1/4 被两个边界拦截——重点验证 < DATE'2026-04-01' 是左闭右开,April 第一秒不会误入。
第 4 步,面试可加一句:写法①对 REG_TIME 上的索引友好(sargable),写法②函数包列必全表扫描——这个结论 MySQL 同样适用。
第 4 题(思考题):空串插入后 WHERE USERNAME='' 的 COUNT 结果
结果是 0,不是 1。两步推理:
- 达梦(非 MySQL 兼容模式)里
''就是 NULL:INSERT ... VALUES('')实际存进去的是 NULL; WHERE USERNAME = ''即USERNAME = NULL,SQL 里任何值与 NULL 比较结果都是 UNKNOWN,一行都不命中 → COUNT = 0。
验证与自救:
SELECT USERNAME, NVL2(USERNAME, '非空', '是NULL') FLAG FROM T_USER; -- 刚插的那行 FLAG = 是NULL
SELECT COUNT(*) FROM T_USER WHERE USERNAME IS NULL; -- → 1,这才是正确的判空姿势这条是 2.7 十大坑的第 2 号:不是语法错,是行为错——MyBatis XML 里 username != '' 这类动态 SQL 判断在达梦全部要改成 username is not null;若开了 COMPATIBLE_MODE=4(第 6 章)行为又对齐 MySQL,所以团队必须事先定死一个口径。
下一章进入多表高级查询与达梦的「数据库编程」:视图、索引、存储过程与包。
