简介:SQL-99标准规范PDF文档,面向数据库管理员、开发人员及数据库技术学习者,帮助系统理解SQL-92向SQL-99演进中的关键变化,解决多媒体数据存储、复杂逻辑判断与新型数据组织等实际需求。文档重点讲解布尔类型及其在逻辑运算中的应用、BLOB/CLOB大型对象对图像音视频数据的支持,以及枚举类型、集合类型、多重集类型、行类型和抽象数据类型等新增特性;同时介绍interval操作符在日期时间处理上的精准扩展,并说明如何保持传统关系数据库的兼容性。资源压缩包共1个文件,PDF格式,总大小483KB,内容紧凑、结构清晰,既可作为系统学习SQL标准演进的参考,也便于开发中快速查阅。已有558人学习下载,适合希望扎实掌握SQL标准核心特性、提升数据库设计与开发能力的读者。
1. 为什么今天还会翻出SQL-99.pdf:这份老标准解决的是方言分裂问题
你手上这份命名为 SQL-99.pdf 的文档,不是一本教人写 SQL 的入门书。它对应的通常是被称作 SQL:1999(别称 SQL3)的国际标准原文或整理稿,是 SQL 标准家族里的第三代版本。它最能回答一个实际的问题:为什么同样的“取前 N 行”,在 Oracle 要用 ROWNUM,在 MySQL 要用 LIMIT,在 SQL Server 要用 TOP——因为 SQL-99 本身没有给出统一语法。适合读它的人有三类:在多个数据库之间搬数据的工程师、需要给团队定 SQL 规范的人、以及想搞懂“哪些报错是标准管不到的”的进阶使用者。读完这份东西,你对数据库方言的容忍度会明显改变。
2. 读懂SQL-99的骨架:ISO/IEC 9075在管什么,和SQL-92怎么分界
很多人下载 SQL-99.pdf 之后的第一反应是直接翻语法,然后发现里面没有索引、没有速查表,像在读一部法规。这是正常反应。标准文档的读者本来就不是冲着“学语法”来的,而是冲着“搞清楚边界”来的。要读懂它,先得知道这份 PDF 大概率只是整个标准的一块切片。
2.1 SQL-99不是一份文档,而是至少五个Part组成的标准族
SQL:1999 的正式编号是 ISO/IEC 9075,它不是一个单独的文件,而是一组分部标准。每个 Part 负责一个领域,之间互相引用。你手里的 SQL-99.pdf,常见的来源是 Part 2 Foundation 的抽印本,或者是把多个 Part 合订在一起的影印版。这解释了为什么你在里面经常找不到想要的东西:因为存储过程在 Part 4,接口规范在 Part 3。
| Part 编号 | 标准名称 | 管什么 |
|---|---|---|
| ISO/IEC 9075-1 | Framework | 基本概念、术语、数据处理总体框架 |
| ISO/IEC 9075-2 | Foundation | 数据定义、数据操作、查询表达式、约束、事务 |
| ISO/IEC 9075-3 | Call Level Interface(SQL/CLI) | 应用程序调用 SQL 的接口规范,ODBC 的思想源头 |
| ISO/IEC 9075-4 | Persistent Stored Modules(SQL/PSM) | 存储过程、存储函数、异常处理、SQLSTATE |
| ISO/IEC 9075-5 | Host Language Bindings | 嵌入式 SQL 语法,宿主语言绑定规则 |
这份划分对从业者的第一个价值是:查问题时先判断问题属于哪个 Part。你在业务代码里遇到的查询写法,基本都在 Foundation 里;存储过程的移植问题,要在 SQL/PSM 里找依据;而客户端“连接数据库用什么接口”这类事,SQL/CLI 才是权威。很多人在 SQL-99.pdf 里搜索“CONNECT”找不到连接语句的标准定义,就是因为连接语义分散在 CLI 和 Framework 两个 Part 里,单看某一页当然看不全。
如果你手上的 PDF 只有 Foundation 部分,也不用觉得缺漏。实际开发中 80% 的语法问题都能在 Foundation 里定位,它定义了 CREATE TABLE、SELECT、INSERT、UPDATE、DELETE、JOIN、CAST、CASE、约束和事务语句。真正需要翻其他 Part 的场景,通常是写存储过程、做数据库驱动、或者研究嵌入式 SQL 的时候。
2.2 从SQL-92的分级到SQL-99的Core与Packages
要理解 SQL-99 的设计意图,要从它前面那个版本说起。SQL-92(ISO/IEC 9075:1992)把实现分成了三个递进级别:入门级(Entry)、过渡级(Intermediate)、完整级(Full)。当时绝大多数数据库只宣称自己是 Entry 级别,因为 Full 级别的覆盖范围太广,谁也不敢打包票。这就带来一个行业怪象:标准分级了,但厂商全挤在最低档,导致“SQL-92 兼容”这个说法没有任何实际约束力。
SQL-99 换了一套路子:它把全部特性拆成一个个带编号的 feature,然后定义了一个必须实现的 Core SQL 子集,其余部分按功能打包,比如 OLAP 包、PSM 包、UDT 包。厂商不需要宣称自己是“SQL-99 完整实现”,只需要说“我兼容 SQL-99 Core,并且支持编号为 xxx 的若干特性包”。你去查 Oracle 或 PostgreSQL 的官方兼容性说明时看到的“Core SQL:2003 兼容”这类字眼,源头就是这套机制。
这套机制一直沿用到了 SQL:2003、SQL:2008、SQL:2016。所以读 SQL-99.pdf 时,心里要有这条分界线:SQL-92 把内容按难度分级,SQL-99 把内容按功能打包,前者是给用户看的大帽子,后者是给实现者和测试者用的清单。今天你在 MySQL 手册里看到“该特性符合 SQL:2003 Core”这样的描述,翻译成人话就是:它达标了,但不是全部达标。
理解了这个背景,你再回头看 SQL-99.pdf 的正文,会发现它和 SQL-92 之间不是彻底的革命。JOIN 语法体系在 SQL-92 已经基本定形,CASE、CAST 也是 SQL-92 引入的。SQL-99 真正补强的是三块:对象关系能力(UDT、类型化表)、分析能力(窗口函数、OLAP 功能)、程序化能力(SQL/PSM 存储过程)。后面第三、四章会分别拆开讲。
3. SQL-99里值得细读的三块内容:对象关系、窗口函数与SQL/PSM
SQL-99 对现代数据库影响最深的内容,集中在三个方向上。第一个是把面向对象带进关系模型,第二个是让“分区排序”这种分析查询有了标准语法,第三个是让存储过程第一次有了国际标准模样。这三块并不是平等的:窗口函数如今人人都在用,SQL/PSM 影响了后来几乎所有数据库的过程化扩展,而对象关系特性则在市场上逐步边缘化。但三个都值得读,因为它们的命运恰恰反映了标准与市场之间的博弈。
3.1 UDT和类型化表:对象关系型特性的标准答案与落地分歧
SQL-99 引入用户自定义类型(UDT,User-Defined Type),并在 Foundation 中定义了完整语法。标准做法是先定义类型,再把类型用作列类型,甚至可以基于 UDT 定义类型化表。下面是最小的一段标准风格示例:
CREATE TYPE address_type AS ( street VARCHAR(100), city VARCHAR(50), zip CHAR(10) );CREATE TABLE customer ( id INTEGER, name VARCHAR(80), addr address_type );第一个代码块定义了一个名为 address_type 的结构化类型,它有三个属性,分别是街道、城市、邮编。第二个代码块把 address_type 当作普通列类型用在 customer 表里,这就是“列类型是用户自定义类型”的写法。注意这里没有用 REFERENCES,也没有外键,因为 UDT 的核心价值是给“值”一个结构,而不是给“表”之间建立关系。如果你看到的是 CREATE TABLE OF 开头的语句,那是在建类型化表,整张表的行结构都由 UDT 决定,走的是另一条路线。
这套设计的本意是让关系数据库能承载更复杂的业务对象,同时保留 SQL 的集合操作能力。但现实里它的落地非常分裂:Oracle 有基于对象类型的实现,PostgreSQL 用复合类型做出了近似能力,MySQL 压根没有原生 UDT。跨库迁移时,类型化表往往是第一个翻车点。后来 ORM 框架兴起,JSON 类型普及,把结构化语义直接放进业务对象里,UDT 这条路线逐渐变成了少数派。读 SQL-99.pdf 的这段内容,不建议把它当必用特性,而是当了解资料:至少知道为什么传统 E-R 建模里没这玩意儿。
3.2 窗口函数:SQL-99里对今天SQL影响最大的语法
窗口函数是 SQL-99 对现代 SQL 最实在的贡献。这个功能以 OLAP 包的形式进入标准,定义了 ROW_NUMBER、RANK、DENSE_RANK 等排序函数,以及 OVER 子句、PARTITION BY、ORDER BY 的使用规则。在这之前,你要算“各部门工资排名”,要么靠应用层排序,要么写复杂的自关联子查询,代码既慢又难维护。
SELECT emp_name, dept_id, salary, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS dept_rank FROM employee;这段查询里,PARTITION BY dept_id 把数据按部门切成一个个窗口,ORDER BY salary DESC 在每个窗口内部按工资倒序排列,RANK() 负责给出排名。这里的 dept_rank 不会因为整张表的排序方式改变而错乱,这是它和 GROUP BY 最本质的区别:GROUP BY 会折叠行,OVER 子句不会减少结果行数,它只是在每行旁边附加一列窗口计算值。
要特别留意 RANK 和 DENSE_RANK 的差异:RANK 遇到并列会跳过后续名次,比如两个第二名之后直接是第四名;DENSE_RANK 则不跳号,紧跟第三名。如果你的业务报表要求排名连续,用 DENSE_RANK;如果要求名次体现人数差距,用 RANK。另一个常见参数是 ROW_NUMBER,它不管并列不平列,给每行一个唯一序号,适用于分页和去重场景。
窗口函数从标准走向产品,花了很多年。Oracle 在分析函数这块起步最旱,SQL Server 和 PostgreSQL 后来跟上,MySQL 则是很晚才补齐。也正因如此,你在 SQL-99.pdf 里看到的窗口函数语法,和 MySQL 8.0 实际支持的情况基本一致,但和 Oracle 的扩展写法(比如各种分析函数前缀)有明显出入。见到不认识的写法,先回标准查它是不是 Core 内容,再查目标库手册,这个顺序能避开一半兼容性问题。
3.3 SQL/PSM与SQL/CLI:存储过程和外部调用的规范原型
SQL-99 的第四部分 SQL/PSM 定义了存储过程、存储函数和触发器的标准语法。这是很多数据库过程化方言的共同鼻祖。Oracle 的 PL/SQL、DB2 的 SQL PL、MySQL 的存储过程语法,都能在这份规范里找到影子,只是各自扩充了细节。
CREATE PROCEDURE adjust_salary( IN p_emp_id INTEGER, IN p_pct DECIMAL(5,2), OUT p_new_sal DECIMAL(10,2) ) BEGIN UPDATE employee SET salary = salary * (1 + p_pct / 100) WHERE emp_id = p_emp_id; SELECT salary INTO p_new_sal FROM employee WHERE emp_id = p_emp_id; END;这段代码是 SQL/PSM 风格的存储过程骨架。IN 表示输入参数,调用方传入 emp_id 和调整百分比;OUT 表示输出参数,过程结束时把新工资放进 p_new_sal 带回调用方。BEGIN...END 是语句块边界,UPDATE 先改工资,SELECT INTO 再把结果取出来赋给输出参数。注意 DECIMAL(5,2) 和 DECIMAL(10,2) 的精度定义分别限制了百分比和小数数值的范围,这是过程参数最容易忽略的细节:参数精度不够会导致运行期截断或报错。
SQL/CLI 则是标准给应用程序提供的调用接口规范。它定义了连接数据库、执行语句、获取结果集的函数模型,你在 JDBC、ODBC 里看到的 connect/execute/fetch 这套流程,和 SQL/CLI 是一脉相承的。理解这一段,对你排查“为什么应用层拿不到结果集”有帮助。很多连接层面的超时报错,根子不在 SQL 本身,而在 CLI 层对游标和事务边界的处理,这部分 SQL-99.pdf 里讲得很细,值得在排查故障时回去翻。
4. 落地SQL-99的兼容性避坑:五个最容易让人翻车的地方
标准的定义是一回事,你正在用的数据库支持到什么程度是另一回事。SQL-99.pdf 里没有留给实现者的弹性空间迈不过去。以下几个坑是我在实际工作中反复见过的,每一条都可以作为排查清单来用。
4.1 把厂商自报的兼容性等级当成全部
现象:产品文档写着“兼容 SQL:1999 Core”,团队就放心把一套 SQL 脚本从 MySQL 迁到 Oracle,结果全表扫描、函数行为全不对劲。原因:“Core 兼容”只代表基础特性集合一致,不代表每个特性在两边的行为一致。比如 Core 里的类型转换规则,MySQL 和 Oracle 对隐式转换的容忍度完全不同。解决:把“兼容性声明”当作起点,而不是终点。迁移前,把项目里用到的每个涉及类型转换、空值处理、字符串比较的语句单独抽出来做冒烟测试,尤其要把隐式转换这类易碎场景列成清单。
4.2 在SQL-99里找LIMIT:标准原文没有这个关键字
现象:工程师在 SQL-99.pdf 里搜“LIMIT”,为什么都搜不到。原因:SQL-99 没有定义 LIMIT 语法。“取前 N 行”这条功能,在标准文本里一直缺位,直到 SQL:2008 才引入了 FETCH FIRST...ROWS 作为统一写法。在此之前,Oracle 用 ROWNUM 伪列,MySQL 用 LIMIT,SQL Server 用 TOP,全是方言。解决:把这个知识当成判断依据。当有人跟你说“取前 N 行是标准功能”时,你可以明确告诉他:标准里没有,这是各厂商自己补的,所以在跨库脚本里要用条件语句分别处理。
4.3 BOOLEAN类型:标准里有,Oracle里没有
现象:按 SQL-99 语法建一张含 BOOLEAN 列的表,在 Oracle 里执行直接报错。原因:SQL-99 把 BOOLEAN 定义为合法数据类型,但 Oracle 一直没提供原生 BOOLEAN 列类型;MySQL 的 BOOLEAN 也只是 TINYINT(1) 的语法糖。标准与实现之间出现了明显的空档。解决:写跨库建表脚本时,用 SMALLINT 加 CHECK 约束来模拟布尔语义,或者在数据库访问层统一做类型映射。不要寄希望于目标库“迟早支持”,你的迁移窗口等不起。
4.4 FULL OUTER JOIN的兼容性尴尬
现象:在 MySQL 里跑 FULL OUTER JOIN 直接语法错误,而同样的 SQL 在 PostgreSQL 里正常。原因:外连接语法在标准里是定义了,但 MySQL 长期没有实现 FULL 外连接,而 LEFT 和 RIGHT 外连接相对普及。解决:在 MySQL 环境下用左外连接和右外连接做 UNION,等价代偿,或者干脆改写业务逻辑。另一个容易踩的变体是 NATURAL JOIN,它要求两边的连接列同名,而不同数据库对“同名但类型不一致”的列处理方式不同,容易产生行为偏差。我一般会建议团队禁用 NATURAL JOIN,强制显式写 USING 或 ON,避免隐性规则带来的兼容问题。
4.5 SQL/PSM的SQLSTATE在跨库移植时不可靠
现象:一套存储过程从 SQL Server 迁到 Oracle,异常处理代码拿到的错误码和原来完全对不上。原因:SQL/PSM 定义了异常处理的框架,但具体 SQLSTATE 值在下层实现时可以自带语义。数据库厂商通常会扩展自己的错误码体系,标准只能兜住外层。解决:移植时不要依赖具体的 SQLSTATE 数字做分支判断,尽量用标准中的 SQLEXCEPTION、SQLWARNING 这种宽泛分类,或者在一个集中的错误映射表里做转换。
这五个坑的共同规律是:标准定义的是蓝图,厂商标注的兼容级别是施工标准,两者之间有大量灰色地带。SQL-99.pdf 能帮你识别这些灰色地带,但它本身不会告诉你坑在哪,你得带着问题去查。
5. 把SQL-99当作工具而不是教材的三个读法
读 SQL-99.pdf 的正确姿势,不是从头到尾读一遍,而是把它当词典、当判据、当对照清单。下面这三个读法是我自己验证过比较顺手的路径。
5.1 把语法规则片段当成报错对照表
当你看到数据库报“unexpected token”或“syntax error”时,把报错位置的上下文拿回 SQL-99.pdf 的语法规则部分去对照。标准文档通常会用带 ::= 的生产式描述语句结构,例如 ::= | UNION 。对比你的语句,看是不是少了某个必须的组成部分,或者把某个子句放错了位置。这个方法尤其适合排查游标、事务、子查询边界这类容易被 IDE 语法高亮掩盖的问题。
5.2 按特性归属决定生产建议
我在评估一个 SQL 写法能不能写进团队规范时,会先用标准做一次归类:
| 判定对象 | 归属层级 | 生产建议 |
|---|---|---|
| FULL OUTER JOIN | SQL-92 完整级,SQL-99 明确列出 | 只用在 PostgreSQL、SQL Server 等明确支持的环境 |
| BOOLEAN 列 | SQL-99 Core 数据类型 | 通用脚本里改用 SMALLINT + CHECK |
| ROW_NUMBER、RANK 等窗口函数 | SQL-99 OLAP 包 | 报表查询可用,迁移前先验证目标库版本 |
| CREATE TYPE 类型化表 | SQL-99 对象关系部分 | 避免作为跨库数据模型的依赖项 |
这个表的核心逻辑是:标准层决定了“这个东西理论上存在”,厂商层决定了“你在当前库能不能用”。两层都有确认了,才轮到“是否值得写进规范”。
5.3 每条SQL过一遍三个问题
最后我养成了一个小习惯:每次写出不确定的 SQL,就问三个问题。第一,这个写法在 SQL-99 原文里有出处吗?第二,目标数据库的手册里是怎么描述它的?第三,如果两边说法对不上,我的兜底方案是什么?只要有一个问题答不上来,就不会把它放进生产脚本。这种做法在几轮数据库跨版本升级里帮了我很多,查标准的时间和下游排查故障的时间相比,性价比高得多。希望帮到你。
本文还有配套的精品资源,点击获取