第3章 高级查询与数据库编程
🎯 本章学习目标
- 掌握多表连接、子查询、窗口函数在达梦中的用法——确认「和 MySQL 8 几乎一致」的部分,记住少量差异。
- 会建视图并理解达梦视图的「可更新性」规则。
- 掌握达梦索引类型(B 树/函数/位图/全文)与失效排查,理解最左前缀两家通用。
- 熟练使用序列的进阶项:CACHE、循环、会话值,理解它相对 AUTO_INCREMENT 的优势场景。
- 能独立编写达梦存储过程 / 函数 / 包 / 触发器,体会「达梦存储编程 ≈ 简化版 Oracle PL/SQL」,与 MySQL 存储过程写法差异点对号入座。
- 建立系统视图速查能力:用
ALL_TABLES / DBA_TAB_COLUMNS / V$替代 MySQL 的information_schema。
3.1 多表查询:先把「相同的部分」放心收下
JOIN(INNER/LEFT/RIGHT/FULL/CROSS)、子查询、UNION/UNION ALL、CTE(WITH)、窗口函数,达梦 DM8 全部支持且语法与 MySQL 8.0 基本一致。直接跑一个「窗口函数取每个用户最近 3 单」的复合例子:
WITH RANKED AS (
SELECT O.ID, O.USER_ID, O.AMOUNT, O.PAY_TIME,
ROW_NUMBER() OVER (PARTITION BY O.USER_ID ORDER BY O.PAY_TIME DESC) RN
FROM T_ORDER O
)
SELECT R.*, U.USERNAME
FROM RANKED R
LEFT JOIN T_USER U ON U.ID = R.USER_ID
WHERE R.RN <= 3
ORDER BY R.USER_ID, R.RN;差异点清单(少见但要知道):
- CONNECT BY 树形查询:MySQL 没有原生层级查询(只能 WITH RECURSIVE),达梦除了 CTE 递归外还支持 Oracle 味
START WITH ... CONNECT BY PRIOR ...,老代码里很常见。 - FULL JOIN:MySQL 不支持,达梦支持——迁移时「左联右联 UNION 拼全连」的土办法可以扔了。
- 排序 NULL 位置:MySQL 默认 NULL 排最前(ASC),达梦默认与 Oracle 一致 NULL 排最后;可用
ORDER BY COL NULLS FIRST/LAST显式指定。
3.2 视图(VIEW)
CREATE OR REPLACE VIEW V_USER_STAT AS
SELECT U.ID USER_ID, U.USERNAME,
COUNT(O.ID) ORDER_CNT, NVL(SUM(O.AMOUNT), 0) TOTAL_AMT
FROM T_USER U LEFT JOIN T_ORDER O ON O.USER_ID = U.ID
GROUP BY U.ID, U.USERNAME;
SELECT * FROM V_USER_STAT WHERE TOTAL_AMT > 1000 ORDER BY TOTAL_AMT DESC LIMIT 20;与 MySQL 的心智一致:普通视图是「存起来的查询」,每次查实时展开。差异:
- 可更新视图规则更严:简单单表视图可 UPDATE/DELETE 透过视图;带聚合/JOIN 的不可(MySQL 类似,但 MySQL 对部分 KEY-DRIVEN 表有特殊放行)。
- WITH CHECK OPTION 支持:透过可更新视图写数据时校验是否满足视图条件。
- 达梦没有 MySQL 5.7 那种「物化视图插件」,需要物化效果用「定时任务 + 中间表」(作业系统 DM 有 JOB 工具,第 6 章提)。
3.3 索引:类型、写法与 MySQL 对照
-- 普通/唯一/组合索引:语法一致
CREATE INDEX IDX_ORDER_USER_TIME ON T_ORDER (USER_ID, PAY_TIME);
CREATE UNIQUE INDEX UK_ORDER_NO ON T_ORDER (ORDER_NO);
-- 函数索引:MySQL 8.0.13+ 才支持,达梦一直有
CREATE INDEX IDX_USER_UPPER ON T_USER (UPPER(USERNAME));
-- 位图索引:MySQL 没有的概念,低基数列(性别、状态)分析型查询利器
CREATE BITMAP INDEX BI_USER_STATUS ON T_USER (STATUS);
-- 全文索引
CREATE FULLTEXT INDEX FT_DOC_CONTENT ON T_DOC (CONTENT);对照认知:
| 项 | MySQL InnoDB | 达梦 |
|---|---|---|
| 主键索引 | 聚簇索引,行数据挂在树上 | 主键建唯一 B 树索引,表数据独立存放(堆组织),无「二级索引回表聚簇」概念但索引命中仍需取行 |
| 最左前缀 | ✅ 铁律 | ✅ 同样成立,联合索引列顺序策略可完全复用 |
| 索引失效场景 | 函数包裹列、隐式转换、前导模糊 | 基本一致;另外注意大小写导致的隐式转换(CHAR 列混用不同字符集函数) |
| 不可见索引 | MySQL 8 有 ALTER TABLE ... ALTER INDEX ... INVISIBLE | 无对应语法,灰度下线索引需先确认应用不再引用再 DROP |
| 查看索引 | SHOW INDEX FROM t | SELECT * FROM DBA_INDEXES WHERE TABLE_NAME='T_USER'; 或管理工具图形树 |
大页大小 = 更大索引扇出
第 1 章说的页大小在这里回马枪:达梦 B 树索引的键值宽度受页大小约束,从 MySQL 迁来的「联合索引四五个字段拼一起」的宽索引,在 8K 页上可能建不出来。这也是建库时敢选 16K/32K 的理由之一。
3.4 序列进阶:三种取值场景
第 2 章已建序列,这里补齐 Java 同学最关心的用法:
CREATE SEQUENCE SEQ_ORDER NOCYCLE CACHE 100 INCREMENT BY 1;
-- 场景1:SQL 里直接用(达梦允许 NEXTVAL 出现在 INSERT/SELECT 值位置)
INSERT INTO T_ORDER (ID, ORDER_NO) VALUES (SEQ_ORDER.NEXTVAL, 'A001');
-- 场景2:先取号再业务使用(如先生成订单号入库、再发消息)
SELECT SEQ_ORDER.NEXTVAL FROM DUAL; -- Java 侧拿到 long 后再 INSERT 指定 ID
-- 场景3:IDENTITY 列手动补数后,重置自增起点
SELECT MAX(ID) FROM T_USER; -- 假设 1000
ALTER TABLE T_USER MODIFY ID RESTART WITH 1001; -- 防主键冲突,MySQL 的 ALTER ... AUTO_INCREMENT=1001 对应物CURRVAL 只能在同会话取过一次 NEXTVAL 之后使用,否则报「序列尚未定义当前值」——MySQL 同学第一次用序列最常撞这条。
3.5 存储过程与函数:达梦是「Oracle 方言」,不是 MySQL 方言
这是本章最大的认知转换。对比同一件小事「按用户 ID 查总金额」:
-- MySQL 风格存储过程(回忆用)
DELIMITER //
CREATE PROCEDURE p_stat(IN p_id BIGINT, OUT p_amt DECIMAL(18,2))
BEGIN
SELECT SUM(amount) INTO p_amt FROM t_order WHERE user_id = p_id;
END //-- 达梦写法:CREATE PROCEDURE ... AS/BEGIN/END(PL/SQL 味)
CREATE OR REPLACE PROCEDURE P_STAT (
P_ID IN BIGINT,
P_AMT OUT DECIMAL(18,2)
)
AS
BEGIN
SELECT NVL(SUM(AMOUNT), 0) INTO P_AMT FROM T_ORDER WHERE USER_ID = P_ID;
END;-- 达梦函数:必须有 RETURN,且函数体里不能做 DML 提交
CREATE OR REPLACE FUNCTION F_USER_AMT (P_ID IN BIGINT)
RETURN DECIMAL(18,2)
AS
V_AMT DECIMAL(18,2);
BEGIN
SELECT NVL(SUM(AMOUNT), 0) INTO V_AMT FROM T_ORDER WHERE USER_ID = P_ID;
RETURN V_AMT;
END;
SELECT F_USER_AMT(1) FROM DUAL; -- 函数可直接进 SELECT调用与查看:
-- disql 里用匿名块调用(出参在块内接收):
DECLARE
V_AMT DECIMAL(18,2);
BEGIN
P_STAT(P_ID => 1, P_AMT => V_AMT); -- 达梦支持 => 关联实参(Oracle 味)
DBMS_OUTPUT.PUT_LINE('总金额=' || V_AMT); -- 需先执行 SET SERVEROUTPUT ON;
END;
-- 看所有存储程序源码(对照 MySQL: SHOW CREATE PROCEDURE)
SELECT * FROM ALL_SOURCE WHERE NAME = 'P_STAT' ORDER BY LINE;MySQL 存储过程 → 达梦改写口诀:
| MySQL 元素 | 达梦对应 | 注意 |
|---|---|---|
DELIMITER // | ❌ 不需要 | 达梦按 END; 识别块结束 |
BEGIN ... END 块 | AS/BEGIN ... END | 结构略不同 |
DECLARE 局部变量在 BEGIN 后 | 变量集中在 AS 段声明 | 位置强制,不像 MySQL 随处 DECLARE |
IF ... THEN ... END IF | 同 | 达梦 IF 还分 SQL 风格/PL 风格 |
WHILE/LOOP | 同 + FOR I IN 1..N 更常用 | PL/SQL 式区间循环 |
HANDLER 异常 | EXCEPTION WHEN OTHERS THEN 块 | 结构更清晰,可分类捕获 |
游标 DECLARE CURSOR | 游标 + OPEN/FETCH/CLOSE | 与 MySQL 心智一致 |
| 临时表 | 支持 CREATE TEMPORARY TABLE | ON COMMIT 行为注意测试 |
3.6 包(PACKAGE):MySQL 完全没有的对象
达梦支持 Oracle 式的包——把一组过程/函数/类型打包管理,有包头(公开接口)+ 包体(实现),做「伪模块化后端代码库」非常好用:
CREATE OR REPLACE PACKAGE PKG_ORDER AS
PROCEDURE CREATE_ORDER (P_USER_ID IN BIGINT, P_AMOUNT IN DECIMAL(18,2), P_ID OUT BIGINT);
FUNCTION USER_TOTAL (P_USER_ID IN BIGINT) RETURN DECIMAL(18,2);
END PKG_ORDER;
/
CREATE OR REPLACE PACKAGE BODY PKG_ORDER AS
FUNCTION USER_TOTAL (P_USER_ID IN BIGINT) RETURN DECIMAL(18,2) AS
V_AMT DECIMAL(18,2);
BEGIN
SELECT NVL(SUM(AMOUNT), 0) INTO V_AMT FROM T_ORDER WHERE USER_ID = P_USER_ID;
RETURN V_AMT;
END;
PROCEDURE CREATE_ORDER (P_USER_ID IN BIGINT, P_AMOUNT IN DECIMAL(18,2), P_ID OUT BIGINT) AS
BEGIN
INSERT INTO T_ORDER (ID, USER_ID, AMOUNT, PAY_TIME)
VALUES (SEQ_ORDER.NEXTVAL, P_USER_ID, P_AMOUNT, SYSDATE)
RETURNING ID INTO P_ID;
END;
END PKG_ORDER;
/
-- 调用带包名前缀(入参出参在匿名块里接收)
DECLARE
V_ID BIGINT;
BEGIN
PKG_ORDER.CREATE_ORDER(1, 99.00, V_ID);
DBMS_OUTPUT.PUT_LINE('新订单ID=' || V_ID);
END;
/
SELECT PKG_ORDER.USER_TOTAL(1) FROM DUAL; -- 函数可直接进 SELECT对 MySQL 开发者的意义:甲方老系统若是 Oracle 迁达梦,后端包里大量 PACKAGE 会原样保留;你新写公共逻辑时,也优先建议收进包而不是散落的独立过程。
3.7 触发器(TRIGGER)
CREATE OR REPLACE TRIGGER TRG_USER_STATUS
BEFORE UPDATE OF STATUS ON T_USER
FOR EACH ROW
BEGIN
IF :OLD.STATUS = 0 AND :NEW.STATUS = 1 THEN
:NEW.CREATE_TIME := SYSDATE; -- 行级触发器里用 :NEW/:OLD 读写新旧值
END IF;
END;| 项 | MySQL | 达梦 |
|---|---|---|
| 时机 | BEFORE/AFTER | BEFORE/AFTER + INSTEAD OF(视图上) |
| 粒度 | 只有 FOR EACH ROW | 行级 + 语句级(默认) |
| 事件组合 | INSERT OR UPDATE 可合写 | 支持,且可按列 UPDATE OF COL |
| OLD/NEW 引用 | OLD.col / NEW.col | :OLD.col / :NEW.col(冒号前缀) |
实践建议与 MySQL 相同:业务逻辑尽量放应用层,触发器只用于审计兜底和历史系统兼容。
3.8 系统视图速查:告别 information_schema
| 需求 | MySQL | 达梦 |
|---|---|---|
| 列出所有表 | SHOW TABLES; | SELECT TABLE_NAME FROM USER_TABLES;(当前模式)/ ALL_TABLES(可访问的)/ DBA_TABLES(全库) |
| 看列结构 | DESC t_user; | SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, NULLABLE FROM USER_TAB_COLUMNS WHERE TABLE_NAME='T_USER';(disql 里也可 DESC T_USER;) |
| 看索引 | SHOW INDEX | USER_INDEXES + USER_IND_COLUMNS |
| 看约束 | information_schema.TABLE_CONSTRAINTS | USER_CONSTRAINTS + USER_CONS_COLUMNS |
| 看视图定义 | SHOW CREATE VIEW | SELECT TEXT FROM ALL_VIEWS WHERE VIEW_NAME='V_USER_STAT'; |
| 看存储过程源码 | SHOW CREATE PROCEDURE | ALL_SOURCE |
| 看参数 | SHOW VARIABLES | SELECT * FROM V$DM_INI;(dm.ini 参数)/ 部分同名 SF_ 函数 |
| 看当前会话 | SHOW PROCESSLIST | SELECT * FROM V$SESSIONS; |
| 看实例状态 | — | V$INSTANCE |
规律:用户视角 USER_ < 可访问 ALL_ < 管理员 DBA_(Oracle 三件套),动态性能视图统一 V$ 前缀。写 Java 巡检脚本时优先 ALL_,DBA 工具用 DBA_/V$。
3.9 本章小结 + 练习
- 查询层(JOIN/CTE/窗口)达梦 ≈ MySQL 8,白送 FULL JOIN、CONNECT BY,注意 NULLS 排序默认差异。
- 索引心智可整体复用(最左前缀通用),白送函数索引、位图索引;页大小影响索引宽度。
- 序列是达梦的主键正道,注意 NEXTVAL 先行、CURRVAL 才可用的规则,以及 IDENTITY 的 RESTART WITH。
- 存储编程整体是 Oracle PL/SQL 方言:块结构、变量声明位置、
:NEW冒号、包对象、ALL_SOURCE 看源码。 - 系统视图三件套 USER_/ALL_/DBA_ + V$ 动态视图,是排查一切的入口。
✍️ 课后练习
- 用
WITH + ROW_NUMBER写出「每个用户消费 TOP3 商品」,再用CONNECT BY重写「部门树查询」(自造一张带 PARENT_ID 的表)。 - 把一段你熟悉的 MySQL 存储过程(含游标或异常处理)改写成达梦版本,标出至少 4 处语法调整点。
- 创建包
PKG_USER(含注册、禁用、查询统计三个接口),并写匿名块调用打印结果。 - 用系统视图查询:当前模式下所有含
AMOUNT列的表及列类型(提示:USER_TAB_COLUMNS WHERE COLUMN_NAME='AMOUNT')。
📖 参考答案
本章答案的订单/商品实验统一用一套自包含的表(前缀
T_3X),在 SHOP 模式下执行即可,不依赖也不污染其他章节的表。
第 1 题:WITH + ROW_NUMBER 写「每用户消费 TOP3 商品」,CONNECT BY 重写部门树
第 1 步,播种输入(商品订单明细 8 行,两个用户):
CREATE TABLE T_3X_ITEM (
USER_ID BIGINT, GOODS VARCHAR(50), AMOUNT DECIMAL(10,2));
INSERT INTO T_3X_ITEM VALUES (1, 'Java实战', 99.00);
INSERT INTO T_3X_ITEM VALUES (1, 'Java实战', 99.00); -- 同一商品买两次,合计 198
INSERT INTO T_3X_ITEM VALUES (1, 'MySQL入门', 59.00);
INSERT INTO T_3X_ITEM VALUES (1, '达梦精解', 79.00);
INSERT INTO T_3X_ITEM VALUES (2, '算法导论', 120.00);
INSERT INTO T_3X_ITEM VALUES (2, '算法导论', 10.00); -- 合计 130
INSERT INTO T_3X_ITEM VALUES (2, 'Redis设计', 45.00);
INSERT INTO T_3X_ITEM VALUES (2, 'Kafka实战', 88.00);
COMMIT;第 2 步,写管道:先聚合出「用户×商品」消费额,再在用户分区内按金额排名取 TOP3:
WITH S AS ( -- 第一层:用户×商品 消费合计
SELECT USER_ID, GOODS, SUM(AMOUNT) TOTAL
FROM T_3X_ITEM GROUP BY USER_ID, GOODS
), R AS ( -- 第二层:分区内排名
SELECT S.*, ROW_NUMBER() OVER (PARTITION BY USER_ID ORDER BY TOTAL DESC) RN
FROM S
)
SELECT USER_ID, GOODS, TOTAL, RN
FROM R WHERE RN <= 3 ORDER BY USER_ID, RN; -- CTE 语法与 MySQL 8 完全一致,直接平移第 3 步,输出对账(手算验证:用户 1 的 Java实战 99+99=198 排第一;用户 2 的算法导论 120+10=130 排第一):
USER_ID GOODS TOTAL RN
------- ---------- --------- ---
1 Java实战 198.00 1
1 达梦精解 79.00 2
1 MySQL入门 59.00 3
2 算法导论 130.00 1
2 Kafka实战 88.00 2
2 Redis设计 45.00 3第 4 步,部门树:建表灌 6 行(老板→总监→经理→员工三层),用 CONNECT BY 查出层级路径:
CREATE TABLE T_3X_DEPT (
ID INT PRIMARY KEY, NAME VARCHAR(30), PARENT_ID INT);
INSERT INTO T_3X_DEPT VALUES (1, 'CEO', NULL); -- 根节点:父为空
INSERT INTO T_3X_DEPT VALUES (2, '技术总监', 1);
INSERT INTO T_3X_DEPT VALUES (3, '产品总监', 1);
INSERT INTO T_3X_DEPT VALUES (4, '研发经理', 2);
INSERT INTO T_3X_DEPT VALUES (5, '测试经理', 2);
INSERT INTO T_3X_DEPT VALUES (6, '工程师', 4);
COMMIT;
SELECT LPAD(' ', 2 * (LEVEL - 1)) || NAME || '(' || ID || ')' AS TREE, LEVEL
FROM T_3X_DEPT
START WITH PARENT_ID IS NULL -- 根:MySQL 8 要靠递归 CTE 手写,达梦白送 CONNECT BY
CONNECT BY PRIOR ID = PARENT_ID
ORDER SIBLINGS BY NAME; -- 同层级按名字排,用 ORDER SIBLINGS 而非 ORDER BY(后者会拆断树形)输出:
TREE LEVEL
------------- -----
CEO(1) 1
产品总监(3) 2
技术总监(2) 2
测试经理(5) 3
研发经理(4) 3
工程师(6) 4第 2 题:MySQL 存储过程改写达梦版,标出至少 4 处语法调整
第 1 步,输入(一段典型的 MySQL 带游标过程:把某用户所有订单金额累加后写回汇总表):
-- MySQL 原版
DELIMITER $$
CREATE PROCEDURE SP_SUM_ORDER(IN p_user_id BIGINT)
BEGIN
DECLARE v_amt DECIMAL(10,2);
DECLARE v_sum DECIMAL(10,2) DEFAULT 0;
DECLARE v_done INT DEFAULT 0;
DECLARE c1 CURSOR FOR SELECT AMOUNT FROM T_ORDER WHERE USER_ID = p_user_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
OPEN c1;
read_loop: LOOP
FETCH c1 INTO v_amt;
IF v_done = 1 THEN LEAVE read_loop; END IF;
SET v_sum = v_sum + v_amt;
END LOOP;
CLOSE c1;
UPDATE T_STAT SET TOTAL = v_sum WHERE USER_ID = p_user_id;
END$$
DELIMITER ;第 2 步,达梦改写(先行建两张实验表方便验证):
CREATE TABLE T_3X_ORDER (ID BIGINT PRIMARY KEY, USER_ID BIGINT, AMOUNT DECIMAL(10,2));
CREATE TABLE T_3X_STAT (USER_ID BIGINT PRIMARY KEY, TOTAL DECIMAL(10,2));
INSERT INTO T_3X_ORDER VALUES (1, 9, 100.00);
INSERT INTO T_3X_ORDER VALUES (2, 9, 50.50);
INSERT INTO T_3X_ORDER VALUES (3, 9, 49.50); -- 合计 200.00
INSERT INTO T_3X_STAT VALUES (9, 0);
COMMIT;
CREATE OR REPLACE PROCEDURE SP_SUM_ORDER (P_USER_ID IN BIGINT) AS
V_AMT DECIMAL(10,2);
V_SUM DECIMAL(10,2) := 0; -- 【调整点②】默认值用 := ,不是 DEFAULT
V_DONE INT := 0;
CURSOR C1 IS SELECT AMOUNT FROM T_3X_ORDER WHERE USER_ID = P_USER_ID; -- 【调整点③】游标在声明段集中声明
BEGIN
OPEN C1;
LOOP -- 【调整点④】LOOP + EXIT WHEN,没有 LEAVE label
FETCH C1 INTO V_AMT;
EXIT WHEN C1%NOTFOUND; -- 【调整点⑤】用 游标%NOTFOUND,不是 HANDLER 置位
V_SUM := V_SUM + V_AMT; -- 【调整点⑥】赋值直接 V_SUM := ...,没有 SET 语句
END LOOP;
CLOSE C1;
UPDATE T_3X_STAT SET TOTAL = V_SUM WHERE USER_ID = P_USER_ID;
COMMIT; -- 【调整点⑦】过程内提交需显式写(或交由调用方控制)
END;
/第 3 步,调用并验证输出:
CALL SP_SUM_ORDER(9);
SELECT * FROM T_3X_STAT WHERE USER_ID = 9; -- TOTAL = 200.00(100+50.5+49.5 手算对账 ✓)第 4 步,差异点清单(超过题面要求的 4 处,面试可全背):
| # | MySQL | 达梦(PL/SQL 风格) |
|---|---|---|
| ① | 变量/游标声明在 BEGIN 后 | 声明在 AS ... BEGIN 之间的声明段 |
| ② | DECLARE v DECIMAL DEFAULT 0 | V DECIMAL := 0 |
| ③ | 游标 DECLARE c CURSOR FOR | CURSOR c IS |
| ④ | read_loop: LOOP ... LEAVE read_loop | LOOP ... EXIT WHEN |
| ⑤ | CONTINUE HANDLER FOR NOT FOUND | 无 HANDLER 此用法,用 c%NOTFOUND |
| ⑥ | SET v = v + x | v := v + x |
| ⑦ | DELIMITER $$ 包裹 | 无 DELIMITER,块尾用 / |
| ⑧ | 异常处理 DECLARE HANDLER | 达梦放在过程末尾独立一段:EXCEPTION WHEN NO_DATA_FOUND THEN ...(Oracle 风格) |
第 3 题:建包 PKG_USER(注册/禁用/查询统计)并匿名块调用
第 1 步,建包规格(接口声明)+包体(实现),直接可跑:
CREATE TABLE T_3X_USER (
ID BIGINT PRIMARY KEY, NAME VARCHAR(50), STATUS SMALLINT DEFAULT 1);
CREATE SEQUENCE SEQ_3X_USER START WITH 101;
CREATE OR REPLACE PACKAGE PKG_USER AS
PROCEDURE P_REGISTER (P_NAME IN VARCHAR, P_ID OUT BIGINT); -- 注册
PROCEDURE P_DISABLE (P_ID IN BIGINT, P_ROWS OUT INT); -- 禁用
FUNCTION F_STAT (P_STATUS IN SMALLINT) RETURN INT; -- 按状态人数
END PKG_USER;
/
CREATE OR REPLACE PACKAGE BODY PKG_USER AS
PROCEDURE P_REGISTER (P_NAME IN VARCHAR, P_ID OUT BIGINT) AS
BEGIN
P_ID := SEQ_3X_USER.NEXTVAL; -- 白拿新号:序列在 PL/SQL 里直接赋值(3.5 知识点)
INSERT INTO T_3X_USER (ID, NAME, STATUS) VALUES (P_ID, P_NAME, 1);
END;
PROCEDURE P_DISABLE (P_ID IN BIGINT, P_ROWS OUT INT) AS
BEGIN
UPDATE T_3X_USER SET STATUS = 0 WHERE ID = P_ID;
P_ROWS := SQL%ROWCOUNT; -- 影响行数用 SQL%ROWCOUNT,对照 MySQL 的 ROW_COUNT()
END;
FUNCTION F_STAT (P_STATUS IN SMALLINT) RETURN INT AS
V_CNT INT;
BEGIN
SELECT COUNT(*) INTO V_CNT FROM T_3X_USER WHERE STATUS = P_STATUS;
RETURN V_CNT;
END;
END PKG_USER;
/第 2 步,匿名块调用(打开 DBMS_OUTPUT 才能看到打印):
SET SERVEROUTPUT ON; -- disql 里先开开关,否则 PUT_LINE 静默丢输出
DECLARE
V_ID BIGINT;
V_ROWS INT;
BEGIN
PKG_USER.P_REGISTER('frank', V_ID);
PKG_USER.P_REGISTER('grace', V_ID); -- 第二次注册,拿 102
DBMS_OUTPUT.PUT_LINE('最后注册ID=' || V_ID); -- → 102(序列从 101 起,验证 NEXTVAL 递进)
PKG_USER.P_DISABLE(101, V_ROWS);
DBMS_OUTPUT.PUT_LINE('禁用影响行=' || V_ROWS); -- → 1
DBMS_OUTPUT.PUT_LINE('启用人数=' || PKG_USER.F_STAT(1)); -- → 1(grace)
DBMS_OUTPUT.PUT_LINE('禁用人数=' || PKG_USER.F_STAT(0)); -- → 1(frank)
COMMIT;
END;
/第 3 步,对照 MySQL:MySQL 没有 PACKAGE 概念,三个接口在 MySQL 里就是三个独立 PROCEDURE/FUNCTION;达梦包的价值在「同一业务的接口集中 + 包体会话级驻留」,Oracle 老系统平移时包基本原样能过。
第 4 题:用系统视图找当前模式下所有含 AMOUNT 列的表
第 1 步,直接按提示查:
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE
FROM USER_TAB_COLUMNS
WHERE COLUMN_NAME = 'AMOUNT'
ORDER BY TABLE_NAME;第 2 步,输出示例(跑完本章各题后应有的形状,具体行以你的环境为准):
TABLE_NAME COLUMN_NAME DATA_TYPE DATA_LENGTH DATA_PRECISION DATA_SCALE
----------- ------------ ---------- ------------ --------------- ----------
T_3X_ITEM AMOUNT DECIMAL 22 10 2
T_3X_ORDER AMOUNT DECIMAL 22 10 2第 3 步,两个延伸:① 跨模式查找把 USER_ 换成 ALL_ 并加 OWNER='SHOP' 条件(USER_ 只看自己模式);② 注意 COLUMN_NAME='AMOUNT' 用大写——因为未加引号的列名存储时自动大写(第 1 章铁律),写 ='amount' 在 CASE_SENSITIVE=Y 库里一行都查不到,这本身就是本章知识点的活验证。
下一章看事务、锁与并发——那是 MySQL 资深用户和达梦之间「行为差异」最集中的地方。
