预见猿份
主题
首页面试题模拟面试SQL练习在线工具关于我们老苗一对一私教学员评价
实战项目
零基础学习路线项目前置基础创新WMS项目Java微服务框架与实战云岚到家项目闪聚支付项目学成在线项目青橙电商项目JVM原理与实战调优分布式事务专题Java高频面试题MySQL从入门到精通达梦数据库从入门到实战MongoDB从入门到实战PostgreSQL从入门到实战TiDB从入门到实战Java数据结构与算法Java 并发编程(JUC)实战课程老苗一对一私教学员评价
blog我的会员
  • 达梦数据库从入门到实战|Java 程序员的国产化数据库迁移指南

    • 课程介绍
    • 第1章 认识达梦与环境搭建
    • 第2章 SQL基础(MySQL对照)
    • 第3章 高级查询与数据库编程
    • 第4章 事务、锁与并发
    • 第5章 Java集成实战
    • 第6章 MySQL迁移与日常运维







----- 到底线了 -----

第2章 SQL 基础(MySQL 对照速成) ​

🎯 本章学习目标 ​

  1. 会在达梦里创建用户/模式,并理解「登录用户 → 同名模式 → 对象归属」这条主线。
  2. 记住 MySQL → 达梦数据类型对照表,知道哪些类型要改写、哪些直接兼容。
  3. 用达梦语法完成建表(DDL)、增删改(DML)、查询(DQL)全流程,每步都能和 MySQL 写法互相翻译。
  4. 掌握 MySQL 的 AUTO_INCREMENT 在达梦中的两种替代:IDENTITY 自增列与序列 SEQUENCE。
  5. 掌握达梦三种分页写法(LIMIT / ROWNUM / OFFSET-FETCH),知道 MyBatis 里该用哪种。
  6. 建立「迁移十大坑」清单,能解释每个坑的成因。

本章所有示例默认在达梦原生模式(COMPATIBLE_MODE=0)下执行。MySQL 兼容模式下部分写法会「睁一只眼闭一只眼」放行,第 6 章专门讲。


2.1 用户、模式与权限:先把「我的东西放哪」搞清楚 ​

MySQL 里你习惯 CREATE DATABASE shop; 然后 USE shop;。达梦里没有 freely 建库一说(库在建实例时已定),对应动作是建用户,自动得到同名模式:

sql
-- 达梦:创建业务用户(会自动创建同名模式 SHOP)
CREATE USER SHOP IDENTIFIED BY "Shop_Pass123";
GRANT RESOURCE TO SHOP;            -- 拥有在本模式下建表等对象权限
GRANT CONNECT TO SHOP;             -- 拥有登录会话的基础权限
1
2
3
4
sql
-- 对照 MySQL
CREATE DATABASE shop;
CREATE USER 'shop'@'%' IDENTIFIED BY 'xxx';
GRANT ALL PRIVILEGES ON shop.* TO 'shop'@'%';
FLUSH PRIVILEGES;
1
2
3
4
5

之后以 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 / LONGTEXTTEXT / CLOB达梦用 CLOB 承接大文本;TEXT 可当别名
BLOB / LONGBLOBBLOB大二进制,JDBC 读写方式见第 5 章 CLOB/BLOB 注意项
DATE(仅日期)⚠️ 达梦 DATE 含时分秒(Oracle 风格)想要纯日期用 DATE 只管存取不管类型?错——达梦 DATE 精确到秒。严格「仅日期」建议 CHAR(8)/DATE 取舍或直接用 TIMESTAMP 自己截断
DATETIMETIMESTAMP(微秒)/ TIMESTAMP(3)(毫秒)Java 的 java.util.Date/LocalDateTime 都映射顺畅
TIMETIME支持
YEAR❌ 无用 SMALLINT 替代
ENUM / SET❌ 无用 VARCHAR + CHECK 或字典表
JSONDM8 支持 JSON 类型与函数(版本相关)低版本用 CLOB 存字符串
BIT(1) / BOOLEANBIT(0/1)达梦布尔语义列习惯用 BIT/NUMBER(1)

达梦 DATE 含时间,是迁移里最隐蔽的坑之一

MySQL 里 DATE 和 DATETIME 泾渭分明;达梦 DATE 实际存储到秒。从达梦把 DATE 列映射成 Java LocalDate 时,时分秒可能被悄悄截掉或比较不等。团队约定:时间一律 TIMESTAMP,纯日期一律 DATE 并在应用层截断,可以躲开所有纠缠。

2.3 DDL:建表 / 改表 / 删表 ​

2.3.1 建表对照 ​

sql
-- 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='用户表';
1
2
3
4
5
6
7
8
sql
-- 达梦等价写法
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启用';
1
2
3
4
5
6
7
8
9
10
11

差异逐条点名:

  1. 没有 ENGINE / CHARSET 子句:引擎唯一,字符集是建库级参数(第 1 章)。
  2. COMMENT 不内联:MySQL 的列内 COMMENT '...' 达梦不认,要用 COMMENT ON。
  3. 自增:AUTO_INCREMENT → IDENTITY(种子,增量),或改用序列(2.3.3 更推荐)。
  4. UNSIGNED 不存在:MySQL BIGINT UNSIGNED 直接写 BIGINT,注意上限差异。
  5. 约束显式命名:好习惯是 CONSTRAINT 名字 UNIQUE/CHECK/FOREIGN KEY,迁移后靠 DBA_CONSTRAINTS 排查。

2.3.2 改表 / 删表:几乎无缝 ​

sql
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 同
1
2
3
4
5
6

回收站

DM8 里 DROP TABLE 默认把表放进回收站(参数 PURGE_AFTER_DROP_TABLE 控制),可 FLASHBACK TABLE 找回——MySQL 没有这个能力,运维时反而要记得:清空空间用 PURGE RECYCLEBIN;。学习阶段觉得意外就把它关掉或习惯 PURGE。

2.3.3 自增主键的达梦正道:序列 ​

IDENTITY 简单,但跨表统一发号、预取号、和 MyBatis 配合这些场景下,序列才是达梦主流方案(对应 Oracle 用法,MySQL 8 之前没有的对象):

sql
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;    -- 取下一个
1
2
3
4
5
6
7
8
方案MySQL达梦选型建议
列自增AUTO_INCREMENTIDENTITY(1,1)小表、单表主键够用
独立发号对象❌ 无原生SEQUENCE高并发、多表共用号段、需要在 INSERT 前就拿 id 的业务
应用发号雪花算法雪花算法不变分库分表项目迁移期维持原状最稳

2.4 DML:增删改 ​

sql
-- 单行插入:完全同 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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

空字符串与 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 打天下;达梦给了三条路:

sql
-- ① 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;
1
2
3
4
5
6
7
8
9
10
11

结论:日常开发直接用 ① LIMIT,写法和 MySQL 一模一样,MyBatis-Plus 的 DM 方言内部也是帮你拼 LIMIT 类语句;② 用于看懂老脚本;③ 用于了解标准。

2.5.2 常用函数对照表 ​

场景MySQL达梦备注
当前时间NOW()SYSDATE / NOW() / CURRENT_TIMESTAMP达梦也认 NOW(),推荐 SYSDATE
空值兜底IFNULL(x, 0)NVL(x, 0) / COALESCEMySQL 兼容模式下 IFNULL 可用
条件表达式CASE WHENCASE WHEN / DECODEDECODE 是 Oracle 味简写
字符串拼接CONCAT(a, b)a || b / CONCAT(a, b)竖线双引号写法最通用
日期→字符串DATE_FORMAT(t, '%Y-%m-%d')TO_CHAR(t, 'YYYY-MM-DD')格式符完全不同,重点记
字符串→日期STR_TO_DATETO_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 开始
类型转换CASTCAST / TO_NUMBER / TO_CHAR
找不匹配REGEXPREGEXP_LIKE(col, '^[0-9]+$')
随机RAND()RAND() / DBMS_RANDOM

TO_CHAR 日期格式符速记(迁移重灾区,MySQL 的 % 风格在达梦统统换成单词):

MySQL达梦含义
%YYYYY四位年
%mMM两位月
%dDD两位日
%H:%i:%sHH24:MI:SS24 小时制时分秒
%s(秒)SSMySQL 的小写 s 在达梦是「世纪」缩写位置,千万别照抄

2.5.3 分组、连接、子查询:语法一致,两处习惯差异 ​

sql
-- 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;
1
2
3
4
5
6
7

差异点只有两个:

  1. ONLY_FULL_GROUP_BY 更严格:MySQL 5.7+ 默认也开了严格模式,但不少团队环境关了;达梦没有「关掉」一说,SELECT 列表里的非聚合列必须全出现在 GROUP BY 中。
  2. 窗口函数完整支持:ROW_NUMBER() OVER(PARTITION BY ... ORDER BY ...) 与 MySQL 8.0 一致,达梦 8 全量可用,第 3 章实战。

2.5.4 UNION / 伪表 DUAL ​

sql
SELECT 'A' X FROM DUAL UNION ALL SELECT 'B' X FROM DUAL;   -- DUAL 照用
SELECT 1;                                                  -- 不写 FROM 也支持(MySQL 习惯可保留)
1
2

注意: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分页只会 LIMITLIMIT 可用,但老脚本多是 ROWNUM看得懂三种写法
4自增AUTO_INCREMENTIDENTITY / SEQUENCE项目统一用序列
5反引号` 包裹字段改双引号 "写 ANSI 风格 SQL
6日期格式符%Y-%m-%dYYYY-MM-DD背 2.5.2 表
7DATE 含时间DATE 仅日期DATE 精确到秒时间统一 TIMESTAMP
8UPDATE...LIMIT常用不支持子查询 + ROWNUM 改写
9GROUP BY可宽松严格全匹配非聚合列全进 GROUP BY
10注释列内 COMMENT ''COMMENT ON 独立语句DDL 脚本二次加工

2.8 本章小结 + 练习 ​

  1. 用户即模式,对象全名 = 模式.表;角色 CONNECT/RESOURCE 管基础权限。
  2. 类型与函数按 2.2、2.5.2 两张对照表「翻译」即可覆盖 90% 日常 SQL。
  3. 主键方案:IDENTITY 求快、SEQUENCE 求活;分页方案:LIMIT 求稳。
  4. 十条坑里最要命的是空串=NULL 与大小写,它们不是语法错误,是行为改变。

✍️ 课后练习 ​

  1. 把你在 MySQL 项目里最熟的一张表(含注释、唯一键、自增主键)改写成达梦 DDL,并解释每一处改动。
  2. 创建序列 SEQ_T_ORDER,用它插入 5 行订单,分别用三种分页写法查「第 2 页 3 条」,比对结果。
  3. 写一句 SQL:查询注册时间是「2026 年 3 月」的用户,分别用 MySQL 思维和达梦思维写,再互查语法错误。
  4. (思考题)INSERT INTO T_USER(USERNAME) VALUES('') 后,SELECT COUNT(*) FROM T_USER WHERE USERNAME='' 的结果是多少?为什么?

📖 参考答案 ​

第 1 题:把 MySQL 表 DDL 改写成达梦版

拿一张典型的 MySQL 表开刀,第 1 步列出输入:

sql
-- 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='商品表';
1
2
3
4
5
6
7
8
9
10

第 2 步,逐处改写(每处都对应本章一个知识点):

sql
-- 达梦版
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 '商品名';
1
2
3
4
5
6
7
8
9
10
11

第 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 步(验证闭环),建完立刻能用三句话验:

sql
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,唯一键可命中
1
2
3

第 2 题:序列插 5 行 + 三种分页写法查「第 2 页 3 条」

第 1 步,建表建序列并灌 7 行(7 行才能显出「第 2 页 3 条」的裁剪效果):

sql
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
1
2
3
4
5
6
7
8
9
10
11
12

第 2 步,三种写法各跑一遍,结果必须完全一致(ID 4、5、6):

sql
-- 写法① 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;
1
2
3
4
5
6
7
8
9
10
11
12
13

三种写法输出均为:

text
ID          AMOUNT
----------- ----------
          4      40.00
          5      50.00
          6      60.00
1
2
3
4
5

第 3 步,心法:写法②的 ROWNUM 必须在包了一层的子查询里且内层带 ORDER BY,直接 WHERE ROWNUM<=6 套在排序前会得到「先取随机 6 行再排序」的错误结果——这个坑在 MySQL 没有(没有 ROWNUM),迁移老 Oracle 脚本时最容易带进来。

第 3 题:查「2026 年 3 月注册」的两种写法互查

第 1 步,播种输入(复用第 2 题风格,新建小表避免碰主教程的 T_USER):

sql
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;
1
2
3
4
5
6

第 2 步,MySQL 思维写法(函数包裹列)在达梦里能跑但错:

sql
-- MySQL 习惯:DATE_FORMAT(t,'%Y-%m')='2026-03'
SELECT DATE_FORMAT(REG_TIME, '%Y-%m') FROM T_REG;   -- 达梦不报错(部分版本),但 '%Y-%m' 不会按 MySQL 解释!
1
2

达梦的 TO_CHAR 用 Oracle 格式符,% 风格直搬要么报错要么输出字面量——正确写法是用达梦思维:

sql
-- 达梦写法①(推荐,索引可用):区间而非函数
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
1
2
3
4
5
6
7

第 3 步,输出对账:两种达梦写法都返回 ID 2、3;ID 1/4 被两个边界拦截——重点验证 < DATE'2026-04-01' 是左闭右开,April 第一秒不会误入。

第 4 步,面试可加一句:写法①对 REG_TIME 上的索引友好(sargable),写法②函数包列必全表扫描——这个结论 MySQL 同样适用。

第 4 题(思考题):空串插入后 WHERE USERNAME='' 的 COUNT 结果

结果是 0,不是 1。两步推理:

  1. 达梦(非 MySQL 兼容模式)里 '' 就是 NULL:INSERT ... VALUES('') 实际存进去的是 NULL;
  2. WHERE USERNAME = '' 即 USERNAME = NULL,SQL 里任何值与 NULL 比较结果都是 UNKNOWN,一行都不命中 → COUNT = 0。

验证与自救:

sql
SELECT USERNAME, NVL2(USERNAME, '非空', '是NULL') FLAG FROM T_USER;   -- 刚插的那行 FLAG = 是NULL
SELECT COUNT(*) FROM T_USER WHERE USERNAME IS NULL;                    -- → 1,这才是正确的判空姿势
1
2

这条是 2.7 十大坑的第 2 号:不是语法错,是行为错——MyBatis XML 里 username != '' 这类动态 SQL 判断在达梦全部要改成 username is not null;若开了 COMPATIBLE_MODE=4(第 6 章)行为又对齐 MySQL,所以团队必须事先定死一个口径。

下一章进入多表高级查询与达梦的「数据库编程」:视图、索引、存储过程与包。

← 第1章 认识达梦与环境搭建第3章 高级查询与数据库编程 →








如果发现文档内容有错误或排版错乱,请及时联系站长老苗修改,不胜感激。联系我们
关于我们 | 隐私政策 | 豫ICP备2026003386号-4 | 豫公网安备41010202004008号
目录

本页无章节