☰
Oracle 19c SQL函数大全查阅与SQL优化避坑指南
2026/11/4 8:40:11 网站建设 项目流程

简介:这份资源是 Oracle Database 19c 官方《SQL Language Reference》PDF 手册,面向数据库开发者、DBA 及备考 OCP 的学习者,用于系统查阅 Oracle 专有 SQL 语法与内置函数。手册按功能梳理了数学、字符串、日期时间、转换、加密与集合聚合等函数类别,涵盖求和求平均、字符串拼接与截取、日期格式转换与运算、数据类型互转以及分组聚合等常用操作,并对大数据、云计算与机器学习相关的新增函数有所体现,可作为日常开发中的案头工具书与排错依据。资源包内共 1 个 PDF 文件,约 14MB,为 E96310-26(2024 年 7 月)版本,目录层级完整,支持按函数名与语法关键字快速定位。目前已有 185 人学习浏览,适合需要随时核对语法细节、补充函数用法与理解官方定义的初中级开发者参考,也便于对照官方口径校验既有脚本。使用时需留意文档中的版权与许可声明,在规定范围内合理引用。

1. 这本 19c SQL 参考手册不是拿来通读的:Oracle 函数大全的正确打开方式

上周帮一个做报表的同事排查一条跑了 40 分钟的 SQL,最后发现原因不是索引,也不是统计信息,而是他把TO_CHAR(create_time,'YYYY-MM-DD') = '2024-07-01'写在了 WHERE 里,索引列被函数包住,直接从范围扫描退化成全表扫描。这种问题的答案其实就写在那份《Oracle Database SQL Language Reference 19c》(E96310-26,2024 年 7 月版)里,只是大部分人把它当成一本需要从头翻到尾的说明书,翻两页就放弃了。这份手册真正的定位是查阅型工具书:第 2 章讲数据类型和隐式转换,第 3 章讲伪列,第 4 到第 6 章讲运算符、表达式和条件,第 7 章才是那份 Oracle 函数大全——从ABS一路排到NUMTOYMINTERVAL,按字母序排列,每条给语法图、用途说明和示例。它解决的不是“我要学 SQL”,而是“我这个函数参数到底怎么传、返回类型是什么、NULL 进去会怎样”这类具体到一行代码的问题,适合已经能写 SQL、但经常在边界条件上翻车的开发者和 DBA。

2. 手册的骨架:第 2 章和第 7 章怎么串起来查

很多人拿到手册第一反应是直接跳到第 7 章函数列表,按字母找自己要用那个,找到语法图抄下来就走。这条路能走通,但只走通一半。函数的行为有一大半不由函数自己决定,而由输入参数的数据类型、会话的 NLS 设置、以及数据库的隐式转换规则决定,这些内容全在第 2 章。

2.1 先读第 2 章再读第 7 章:类型规则决定了函数返回什么

第 2 章开头是 Oracle 内建数据类型清单:CHAR、VARCHAR2、NUMBER、DATE、TIMESTAMP、INTERVAL、RAW、LOB、ROWID、UROWID,再往后是扩展数据类型、用户自定义类型和 Oracle 自带类型(ANYDATA、XMLType、SDO_GEOMETRY这些)。真正要盯住的是Data Type Comparison Rules和Data Type Precedence两节,它们规定了当两个不同类型的值放在一起比较或运算时,谁向谁靠拢。

举个最常见的:NUMBER的优先级高于VARCHAR2,所以WHERE num_col = '123'会把左边的数字列……不,是把右边的字符串隐式转成数字,索引还能用。反过来WHERE char_col = 123会把字符列转成数字,索引直接失效,而且如果char_col里有一行是非数字内容,运行时抛ORA-01722: invalid number。这条规则在手册的 Implicit Data Conversion 表格里有明确矩阵,值得打印出来贴在显示器旁边。

验证方法不用猜,用手册第 7 章的DUMP和VSIZE直接看字节:

-- DUMP 第二个参数是输出进制,16 表示十六进制;返回类型字节 + 长度 + 内部表示 SELECT DUMP('123', 16) AS char_literal, DUMP(123, 16) AS num_literal, DUMP(TO_NUMBER('123'),16) AS converted, VSIZE('123') AS bytes_of_char FROM dual;

DUMP的第一个参数可以是任意表达式,第二个参数省略时默认十进制。返回结果里第一个数字是类型编码,VARCHAR2是 1,NUMBER是 2,DATE是 12。这条语句的价值在于:当你怀疑某个字段被隐式转换了,直接DUMP一下就能确认它到底以什么类型参与运算,比看执行计划还快。

还有一个容易被忽略的点:Oracle 里长度为 0 的VARCHAR2就是NULL,两者不可区分。所以WHERE name = ''永远不成立,必须写IS NULL。这条在手册第 2 章的 Nulls 小节写得很清楚,但每年仍有大量代码在这上面栽跟头。

2.2 三条检索路径:目录、语法图、字母顺序清单

手册提供了三种完全不同的进入方式,用错了会浪费大量时间。我把它们整理成一张表:

检索路径位置适合解决的问题明显短板
章节目录(Contents)PDF 前部知道大致分类,想系统看一类内容函数按字母排,不按业务场景排
语法图(Syntax)每个函数条目开头参数顺序、可选参数、能否省略不告诉你返回类型和 NULL 行为
字母顺序清单第 7 章已知函数名,快速定位不知道函数名时完全没法用

实际工作中最有效的顺序是:先按业务场景确定函数名,再用字母序定位,读语法图确认参数,最后回到正文的 Purpose 和 Examples 确认边界行为。比如要做字符串截取,先知道有SUBSTR,翻到字母 S 区,看语法图SUBSTR(char, position [, substring_length]),发现第三个参数可选,再读正文确认 position 为负数时从尾部倒数。

很多人卡在“不知道函数名”这一步。这时候目录里第 7 章开头的分类清单就有用了:Numeric Functions、Character Functions Returning Character Values、Character Functions Returning Number Values、Datetime Functions、Conversion Functions、NULL-Related Functions、Aggregate Functions、Analytic Functions、JSON Functions等等。按类别扫一遍,比在几十页字母序里瞎翻快得多。

2.3 语法图(Syntax Diagram)的正确读法

语法图是这份手册里信息密度最高的部分,也是最容易被跳过的部分。它的符号规则是固定的:

符号或写法含义
大写单词关键字,必须原样写
小写斜体占位符,替换成具体值
方括号[ ]可选项,可以整段省略
花括号{ }多选一,必须选且只选一个
竖线 ``
省略号...可重复任意次

拿SUBSTR举例:SUBSTR(char, position [, substring_length])。方括号说明第三个参数可以不传,此时从 position 一直截到字符串末尾。position为 0 时按 1 处理,为负数时从尾部往前数。这些细节在语法图里看不出来,得往下读两段正文。

提示:读语法图时顺手看一眼该函数条目末尾的「Related Topics」,手册作者会把功能相近的函数互相链接起来。查NVL会链到COALESCE和NULLIF,查RANK会链到DENSE_RANK和ROW_NUMBER,这个链接网络比自己在网上搜要准。

一个实操习惯:把语法图里的方括号和花括号抄到自己的笔记里,用同样的记号标注参数必填/可选。等到线上出问题回头查的时候,这种带标记的笔记比原始语法图好用得多。

3. 常用函数落地:把第 7 章重排成四组可直接抄的写法

第 7 章的排序是给检索用的,不是给使用用的。真正写代码时,函数是按业务场景成组出现的。我把日常用得最多的四组抽出来,每组给出可直接跑的示例、参数说明和选型理由。

3.1 字符处理与正则:INSTR、SUBSTR、REGEXP_LIKE 的组合

字符串处理是 Oracle 函数大全里条目最多的一块。Character Functions Returning Character Values和Character Functions Returning Number Values这两个分类的划分标准很实用:返回字符的(SUBSTR、UPPER、TRIM、LPAD)和返回数字位置的(INSTR、LENGTH)分开记。前者用来构造输出,后者用来做条件判断。

-- 从 "订单号|客户名|金额" 这种拼接串里拆出三段 WITH t AS (SELECT 'SO20240701|某客户|12800.50' AS raw_str FROM dual) SELECT SUBSTR(raw_str, 1, INSTR(raw_str, '|') - 1) AS order_no, SUBSTR(raw_str, INSTR(raw_str, '|') + 1, INSTR(raw_str, '|', 1, 2) - INSTR(raw_str, '|') - 1) AS cust_name, SUBSTR(raw_str, INSTR(raw_str, '|', 1, 2) + 1) AS amount FROM t;

INSTR(str, substr, position, occurrence)四个参数分别是源串、目标串、起始位置、第几次出现。这里INSTR(raw_str,'|',1,2)表示从第 1 个字符开始找第 2 个竖线,返回它的绝对位置。SUBSTR的第二个参数是起始位置(从 1 开始),第三个是长度,不是结束位置——这是新手最容易搞错的地方。

正则部分,REGEXP_LIKE用得多,但真正的效率差异在REGEXP_SUBSTR的第六个参数(subexpression):

-- 提取日志里的 IP 和耗时,match_param 'i' 表示大小写不敏感 SELECT REGEXP_SUBSTR(log_line, '(\d+\.\d+\.\d+\.\d+)', 1, 1, NULL, 1) AS ip, REGEXP_SUBSTR(log_line, 'cost=(\d+)', 1, 1, NULL, 1) AS cost_ms FROM app_log WHERE REGEXP_LIKE(log_line, '^SLOW', 'i');

第六个参数subexpression为 1 表示只取第一个捕获组的内容,省掉一次REGEXP_REPLACE。代价是正则本身比INSTR+SUBSTR慢一个数量级,几十万行以上就要掂量。我的经验是:定长分隔符用INSTR/SUBSTR,变长模式才上正则。

3.2 日期时间函数:ADD_MONTHS 和 MONTHS_BETWEEN 的边界

日期函数里最容易出错的是月末行为。ADD_MONTHS(date, n)在目标月份没有对应日期时会自动取该月最后一天:1 月 31 日加一个月返回 2 月 28 日(非闰年)或 29 日(闰年),不会报错也不会返回 3 月 3 日。

-- 月末行为验证 + 按自然月截断 SELECT ADD_MONTHS(DATE '2024-01-31', 1) AS plus_1m, ADD_MONTHS(DATE '2024-01-31', -1) AS minus_1m, LAST_DAY(DATE '2024-02-15') AS last_day_feb, TRUNC(DATE '2024-07-15', 'MM') AS month_start, EXTRACT(YEAR FROM DATE '2024-07-15') AS yr, MONTHS_BETWEEN(DATE '2024-07-31', DATE '2024-06-30') AS mb FROM dual;

MONTHS_BETWEEN(d1, d2)返回d1 - d2的月数,带小数。规则是:两个日期日分量相同或都是所在月最后一天时返回整数,否则按 31 天一个月折算小数。上面这个例子里 7 月 31 日和 6 月 30 日都是月末,返回 1,不是 1.03。这个细节直接决定了算账龄、算逾期天数时结果对不对。

另一个需要留神的点是DATE和TIMESTAMP的混用。SYSDATE返回DATE,只到秒;SYSTIMESTAMP返回带时区的TIMESTAMP。做跨时区系统时应该统一用SYSTIMESTAMP配合FROM_TZ和AT TIME ZONE,手册第 7 章这几个函数是连着的。

3.3 NULL 与条件分支:NVL、COALESCE、DECODE、CASE 怎么选

这四个函数经常被混用,但行为差异不小,尤其是返回类型。

SELECT NVL(comm, 0) AS nvl_demo, COALESCE(comm, bonus, 0) AS coalesce_demo, NULLIF(actual, target) AS nullif_demo, DECODE(status, 'A', '激活', 'I', '停用', '未知') AS decode_demo, CASE WHEN status = 'A' THEN '激活' WHEN status = 'I' THEN '停用' ELSE '未知' END AS case_demo FROM emp_test;

选型逻辑:

场景推荐原因
两个参数二选一NVL最短,Oracle 原生优化好
多个参数取第一个非空COALESCE支持变参,且符合 SQL 标准
相等则返回 NULLNULLIF避免除零的经典写法x / NULLIF(y,0)
等值映射、分支少DECODE写法紧凑
范围判断、分支多CASE支持>、<、BETWEEN

DECODE有个隐藏行为:它会把所有返回值统一转换成第一个结果参数的类型。如果第一个分支返回数字、第二个返回字符串,DECODE会隐式转换,可能出现意料之外的结果。CASE则要求各分支类型兼容,编译器会直接报错。所以在类型不确定的场景下,CASE更安全。

注意:NOT IN的子查询里如果出现NULL,整个条件返回空集,一行都不会命中。这是 SQL 三值逻辑的经典陷阱,手册第 6 章的 Null Conditions 一节有完整说明。稳妥做法是子查询里加WHERE col IS NOT NULL,或者改用NOT EXISTS。

3.4 聚合与分析函数:LISTAGG、ROW_NUMBER、LAG 的窗口写法

LISTAGG是把多行拼成一行的标准做法,19c 支持ON OVERFLOW子句处理超长结果:

-- 按部门把员工名拼成一行,超过 4000 字节时截断并加省略标记 SELECT dept_id, LISTAGG(emp_name, ', ' ON OVERFLOW TRUNCATE '...') WITHIN GROUP (ORDER BY emp_name) AS names FROM emp_test GROUP BY dept_id;

不加ON OVERFLOW时,拼接结果超过VARCHAR2上限(4000 字节,扩展类型下 32767)会抛ORA-01489: result of string concatenation is too long。这是生产环境里真实发生频率很高的一类报错,尤其是在给下游导出“一行一主键”的宽表时。

分析函数里,ROW_NUMBER、RANK、DENSE_RANK三者的区别值得写进肌肉记忆:

SELECT emp_name, salary, dept_id, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rk, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS drk, LAG(salary, 1, 0) OVER (PARTITION BY dept_id ORDER BY salary DESC) AS prev_sal FROM emp_test;

ROW_NUMBER严格递增不重复,适合去重取第一条;RANK并列后跳号(1,1,3);DENSE_RANK并列后不跳号(1,1,2)。LAG(expr, offset, default)取同一分区内往前第 offset 行,越界时返回 default,用来算环比、算相邻记录差值非常顺手,比自连接快得多。

4. 伪列与 Hint:手册里最容易被跳过的两章怎么用在调优上

第 3 章伪列和第 2 章里的 Hints 清单,是这本手册里最不“函数”、但实战价值最高的两块内容。前者影响结果集,后者影响执行计划。

4.1 伪列:ROWNUM、ROWID、LEVEL、ORA_ROWSCN

ROWNUM的赋值时机是在行被查询出来后、ORDER BY之前。所以下面这个写法是错的:

-- 错误:先取前 10 行再排序,结果不是工资最高的 10 个人 SELECT * FROM emp_test WHERE ROWNUM <= 10 ORDER BY salary DESC; -- 正确:先排序,在外层限制行数(19c 也可直接用 FETCH FIRST) SELECT * FROM ( SELECT * FROM emp_test ORDER BY salary DESC ) WHERE ROWNUM <= 10; -- 19c 推荐写法,可读性更好 SELECT * FROM emp_test ORDER BY salary DESC FETCH FIRST 10 ROWS ONLY;

19c 支持FETCH FIRST n ROWS ONLY和OFFSET n ROWS FETCH NEXT m ROWS ONLY,这是标准语法,走的是同样的执行路径但语义清晰。分页场景下用OFFSET时要注意:偏移量很大时性能会线性下降,因为前面的行仍要生成。

ROWID是行的物理地址,用来做超快速定位:

-- 用 ROWID 定位并更新,比主键索引还快,但跨版本不可移植 UPDATE emp_test SET salary = salary * 1.05 WHERE ROWID = 'AAAR3qAAEAAAACXAAA';

ROWID在表重建、分区移动、SHRINK SPACE之后会变,所以只能在同一事务内临时用,绝不能存到业务表里当主键。这一点手册的ROWID Pseudocolumn条目里写得很明白。

LEVEL配合CONNECT BY做层级查询,ORA_ROWSCN用来做增量抽取的乐观判断——但要注意它默认是块级(block-level)的,同一块内任何一行更新,整块的行ORA_ROWSCN都会变。要精确到行,建表时必须加ROWDEPENDENCIES:

-- 建表时声明行级 SCN,之后 ORA_ROWSCN 才是行粒度 CREATE TABLE t_scn (id NUMBER, val VARCHAR2(50)) ROWDEPENDENCIES; -- 按 SCN 做增量抽取 SELECT id, val FROM t_scn WHERE ORA_ROWSCN > 1234567890;

4.2 Hint 速查:那几十个 Hint 里真正要记的就十来个

第 2 章的 Hints 小节按字母顺序列了七十多个 Hint,从ALL_ROWS到USE_NL_WITH_INDEX。全部记住不现实,但有一组是日常调优必用的:

Hint作用典型写法
INDEX指定走某个索引/*+ INDEX(t idx_t_col) */
FULL强制全表扫描/*+ FULL(t) */
LEADING指定连接顺序的第一张表/*+ LEADING(t1 t2) */
USE_NL嵌套循环连接/*+ USE_NL(t2) */
USE_HASH哈希连接/*+ USE_HASH(t2) */
PARALLEL并行度/*+ PARALLEL(t 4) */
OPT_PARAM语句级改优化器参数/*+ OPT_PARAM('optimizer_index_cost_adj' 20) */

Hint 写在SELECT之后、列清单之前,格式错了会被静默忽略,不报错。这是排查 Hint 失效时最难搞的一点:你没法从错误信息里知道它没生效。验证方法是看执行计划的Note段,或者用DBMS_XPLAN.DISPLAY_CURSOR加+ALIAS格式。

-- 加 Hint 后立刻对比执行计划,确认 Hint 是否真的被接受 SELECT /*+ INDEX(e idx_emp_dept) */ emp_name, salary FROM emp_test e WHERE dept_id = 10; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'TYPICAL +ALIAS'));

如果Note段出现unused hint之类的提示,说明 Hint 语法或者对象别名写错了。另一种常见情况是 Hint 生效了但优化器算了算成本觉得不划算,仍然不采纳,这时候要配合OPT_PARAM调整成本参数。

4.3 条件与 JSON 章节:19c 新增能力的检索入口

第 6 章 Conditions 里除了常规的BETWEEN、EXISTS、IN,还单独列了 SQL For JSON Conditions:IS JSON、JSON_EQUAL、JSON_EXISTS、JSON_TEXTCONTAINS。这一块是 19c 相对早期版本增强比较明显的地方,配合第 7 章的JSON_TABLE、JSON_VALUE、JSON_QUERY、JSON_MERGEPATCH,可以不走应用层直接在库里解析 JSON。

-- 建表时给 JSON 列加校验,写入非法 JSON 直接报错 CREATE TABLE t_json ( id NUMBER PRIMARY KEY, doc CLOB CHECK (doc IS JSON) ); -- 用 JSON_TABLE 把 JSON 数组展开成关系行 SELECT jt.order_no, jt.amount FROM t_json t, JSON_TABLE(t.doc, '$.orders[*]' COLUMNS ( order_no VARCHAR2(32) PATH '$.orderNo', amount NUMBER PATH '$.amount' )) jt WHERE JSON_EXISTS(t.doc, '$.orders[*]?(@.amount > 1000)');

JSON_TABLE的第二个参数是 JSONPath,COLUMNS子句里用PATH指定每个字段的取值路径。这条语句把 JSON 数组直接变成可以 JOIN 的虚拟表,配合JSON_EXISTS做过滤,避免了在应用层做一次完整的反序列化。性能上比把 JSON 捞出来在代码里解析要好,因为过滤条件下推到了存储层。

5. 避坑:五个把手册读歪的现场与改正动作

这一章记录的是我这些年实际踩过、或者帮别人排查过的例子。每一条都按“看到什么现象 → 根因是什么 → 怎么改”来写。

5.1 隐式转换与 NLS 相关的坑

现象一:同一条TO_DATE('2024-07-01','YYYY-MM-DD'),在开发库正常,换到另一个环境报ORA-01861: literal does not match format string。

原因不是函数用错了,而是某处省略了格式串,直接写TO_DATE('2024-07-01'),此时 Oracle 按会话的NLS_DATE_FORMAT解析。开发库是YYYY-MM-DD,生产库是DD-MON-RR,同一个字面量就解析不了。

解决:永远显式带格式串,不要依赖会话设置。同时把NLS_DATE_FORMAT、NLS_NUMERIC_CHARACTERS这两个参数写进上线检查清单。

-- 查当前会话的 NLS 设置,排查环境差异时第一件事 SELECT parameter, value FROM nls_session_parameters WHERE parameter IN ('NLS_DATE_FORMAT','NLS_DATE_LANGUAGE','NLS_NUMERIC_CHARACTERS');

现象二:WHERE TO_CHAR(create_time,'YYYY-MM-DD') = '2024-07-01'查询跑了几十秒,create_time上明明有索引。

原因是函数包住了索引列,普通 B-Tree 索引失效。两种改法:改成范围条件create_time >= DATE '2024-07-01' AND create_time < DATE '2024-07-02',或者建函数索引CREATE INDEX idx ON t(TO_CHAR(create_time,'YYYY-MM-DD'))。前者更通用,后者适合报表类固定格式查询。手册第 2 章 Implicit Data Conversion 表格里明确了哪些转换会阻止索引使用,值得逐行看一遍。

5.2 分页与排序顺序的坑

现象:分页查询第 2 页出现了第 1 页已经出现过的记录,或者某些记录永远查不到。

根因是ORDER BY的字段有重复值,排序不稳定,两次查询的物理顺序不同。ROWNUM是查询过程中动态赋值的,一旦排序键不唯一,翻页结果就不可复现。

解决:排序键必须加上唯一列兜底,比如ORDER BY create_time DESC, id DESC。另外,ROWNUM分页必须写成三层嵌套(排序 → 限上界 → 滤下界),少一层就会出错:

-- 第 11 到 20 行,排序键必须唯一 SELECT * FROM ( SELECT a.*, ROWNUM rn FROM ( SELECT id, name, create_time FROM biz_order ORDER BY create_time DESC, id DESC ) a WHERE ROWNUM <= 20 ) WHERE rn > 10;

5.3 NULL 参与运算和聚合的坑

现象:报表里平均分和手工算的对不上,或者SUM结果偏大。

根因是聚合函数一律忽略NULL。AVG(score)的分母是COUNT(score)而不是COUNT(*),有NULL时结果会偏高。SUM全部为NULL时返回NULL而不是 0,前端拿到NULL直接显示空白。

解决:明确业务语义后选择AVG(NVL(score,0))或者NVL(AVG(score),0),两者结果不同,取决于你想让NULL参与分母还是不参与。这类问题没有标准答案,但必须在需求评审时确定下来,不能留给线上猜。

5.4 DECODE 返回类型被统一转换的坑

现象:DECODE在某些行返回数字、某些行返回字符串,结果全部变成了数字,字符串分支变成NULL或报错。

根因是DECODE会把所有返回值统一转换为第一个结果参数的类型。这是DECODE和CASE最本质的差异之一。

解决:类型不一致的分支一律改用CASE,让编译器在解析阶段就报类型不兼容,而不是在运行时静默转换。这条在手册DECODE条目的 Examples 里有暗示,但不仔细看很容易漏。

注意:DECODE里NULL和NULL是相等的(DECODE(NULL, NULL, 1)返回 1),这点和=运算符不同。用它做空值判断可以,但要知道这是个特例。

5.5 LISTAGG 超长与排序不确定的坑

现象:一段聚合 SQL 平时跑得好好的,某天数据量涨上来突然报ORA-01489。

根因是LISTAGG的拼接结果超过了VARCHAR2的 4000 字节上限。这不是数据错误,是容量问题,什么时候触发取决于数据分布,非常随机。

解决:加ON OVERFLOW TRUNCATE(19c 支持),或者改用JSON_ARRAYAGG返回CLOB,再或者把聚合拆到应用层做。前两种能立刻止血,第三种才是根治。

另一个隐患是WITHIN GROUP (ORDER BY ...)的排序键不唯一时,拼接顺序不稳定,两次执行结果不同。如果下游要对这个字符串做比对或落库,必须保证排序键唯一。

6. 进阶:把 PDF 函数索引抽成 CSV,做成可检索速查表

手册 PDF 有几百页,检索体验其实一般:搜SUBSTR会命中几十处引用,得手动翻到正文条目。我的做法是把它转成一份带分类和参数签名的 CSV,再导进任意支持全文检索的工具里。

先抽出文本,再按函数条目的固定模式匹配。每个函数条目在文本里的结构基本是:函数名单独一行,紧跟一段 Purpose,然后是 Syntax,最后是 Examples。

# 第一步:把 PDF 转成纯文本,保留分页符便于回溯 pdftotext -layout -enc UTF-8 "sql-language-reference-19c.pdf" sqlref.txt # 第二步:统计各章的起始页,方便定位 grep -n "^[0-9]\+ *Functions$\|^[0-9]\+ *Basic Elements" sqlref.txt | head -20

-layout参数是关键,它保留原始排版,语法图里的缩进和竖线才不会串行。不加这个参数,语法图会变成一坨,后续正则全废。

然后用一段脚本,按“全大写函数名 + 独立成行”的规则抓函数名和所属分类:

import re, csv # 函数名条目在文本中的特征:整行只有大写字母/数字/下划线,长度 2-40 FUNC_PAT = re.compile(r'^([A-Z][A-Z0-9_]{1,39})$', re.M) # 分类标题形如 "Numeric Functions"、"JSON Functions" CLASS_PAT = re.compile(r'^([A-Z][A-Za-z ]+) Functions$', re.M) with open('sqlref.txt', encoding='utf-8') as f: text = f.read() # 记录每个分类标题的位置,后面用区间判断函数归属 marks = [(m.start(), m.group(1)) for m in CLASS_PAT.finditer(text)] rows = [] for m in FUNC_PAT.finditer(text): pos, name = m.start(), m.group(1) # 跳过 SQL、NULL、DATE 这类关键字,只留真正的函数条目 if name in {'SQL', 'NULL', 'DATE', 'LEVEL', 'ROWID', 'NOT', 'AND', 'OR'}: continue owner = next((c for p, c in reversed(marks) if p < pos), 'UNKNOWN') rows.append({'function': name, 'category': owner}) with open('oracle_funcs.csv', 'w', newline='', encoding='utf-8') as out: w = csv.DictWriter(out, fieldnames=['function', 'category']) w.writerow({'function': 'FUNCTION', 'category': 'CATEGORY'}) w.writerows(rows) print(f'提取 {len(rows)} 条,去重后 {len({r["function"] for r in rows})} 个函数')

这段脚本的核心是两件事:用分类标题的位置做区间划分,判断每个函数属于哪一类;用关键字黑名单过滤误匹配。实际跑下来会有一批噪声,比如正文里大写的表名、列名也会被匹配到,所以最后一步一定要人工抽样核对,随机抽 20 个函数回到 PDF 里比对语法图,确认提取的是条目而不是引用。

有了这份 CSV,后续能做的事情就多了:按 category 分组生成自己的速查卡;把 function 列丢进检索工具建立全文索引;甚至反过来用函数名去正则匹配自己项目里的 SQL 代码,统计哪些函数用得最多、哪些从没出现过。

提示:提取脚本里的黑名单要根据实际文本调整,不同版本的手册排版会有差异。第一次跑完先看UNKNOWN分类有多少条,如果占比超过 20%,说明分类标题的正则没匹配上,得先修这个再谈函数提取。

从那以后我每次拿到新版手册,第一件事不是读,而是先跑一遍提取脚本,把它变成一份能按分类筛选、能全文搜索的表格,再回头对照官方语法图核对。这个习惯帮我省掉了大量“记得有这么个函数但想不起参数顺序”的时间——希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询