SQL 不是 Oracle 的发明,是一套有编号的标准。 四类语句按「动了什么」分:DDL 动结构、DML 动数据、DCL 动权限、TCL 动事务边界。 这个分类不是学术游戏,是操作后果的分级:DDL 一执行就隐式提交、DML 还能回滚、 DCL 改错别人立刻受影响。
上一篇在 SQLPlus 里认路,这一篇开始认话。 第 3 章的每条命令都是「怎么用这个工具」,从这一章起全是「怎么让数据库干活」。 这一篇只讲规矩,不讲查询*——因为规矩错了,后面每条 SQL 都会以奇怪的方式失败。 真正的查询从 4.3 开始。
一、四类语句,按「动了什么」分
SQL 的全称是「结构化查询语言」,但它不只管查询。 按作用对象分四类,这个分法几乎所有数据库教材都这么划:
| 类别 | 全称 | 动什么 | 代表语句 |
|---|---|---|---|
| DDL | 数据定义语言 | 结构:建、改、删对象 | create/alter/drop/truncate |
| DML | 数据操纵语言 | 数据:增、删、改、查 | insert/update/delete/select |
| DCL | 数据控制语言 | 权限:给权、收权 | grant/revoke |
| TCL | 事务控制语言 | 事务边界:提交、回滚 | commit/rollback/savepoint |
为什么 DDL 里混进了 truncate? 因为它清空表的方式是「重建存储」,
本质上是改结构而不是删行——这个细节决定了它不能回滚,4.6 节会细讲。
还有一类常被单列的:DQL(数据查询语言),就是指 select。
教材把 select 归进 DML,因为它最常用、也最像「操作数据」。
两种分法都对,看到 DQL 这个缩写别慌,它就是 select。
二、为什么 DDL 和 DML 的区别最要命
四类里只有这两类的边界会咬人。 记住一句话:DDL 一执行就隐式提交。
意思是:你之前攒着没 commit 的一堆 insert,在你敲下 create table 的那一瞬间,
全部被默默提交了。 你根本没写 commit,但它替你写了。
带来的后果是:drop table 之后 rollback 救不回来。
不是「Oracle 忘了做回滚」,是「它根本没有回滚这个动作」——DDL 只改数据字典,不写回滚日志。
所以生产库上做结构变更有个铁规矩:先备份,再做。
因为你能依靠的只有备份,没有 rollback。这一条在 13 章讲事务时会再展开一次。
三、第一条规矩:关键字不分大小写,数据分
select/SELECT/Select 是同一个词。但 'CLERK' 和 'clerk' 是两个完全不同的值。
关键字是语法,字符串是数据——语法怎么变都认,数据一个字母都不能错。
Oracle 还多一层:不加引号的表名、列名会被自动转成大写存。
所以 create table emp 之后,数据字典里记的是 EMP。
想保留小写,得加双引号:create table "emp"——
但从此每次引用都必须带双引号,很麻烦,不建议。
习惯上统一写成「关键字大写、表名列名小写」,
一眼就能分出哪部分是语法、哪部分是你自己起的名字。
这套习惯在 SQL 里比在任何编程语言里都值钱,因为 SQL 没有语法高亮的时候太多了。
四、第二条规矩:分号是一条语句的终点
分号不是 SQL 语法的一部分,是「我说完了」的信号。
SQL*Plus 收到分号,才把攒着的那几行一起发给数据库。
所以一条语句可以随便折行——只要最后有分号。
这也解释了那个经典现象:忘了打分号回车,光标停在 2 不动。
它不是卡死,是在等你把话说完。补一个分号,回车就执行了。
写脚本时要注意:分号是分隔符,所以字符串里的分号会被误判。
insert into t values ('a;b') 在某些工具里会被从中间切断。
规范做法是把脚本存成文件用 @ 执行,让 SQL*Plus 自己按规则解析。
五、第三条规矩:注释两种,一种不能嵌套
单行注释 -- 管到本行末尾,多行注释 /* */ 管一段。
两种都可以出现在语句中间,解析器会直接跳过。
注意 -- 后面要跟一个空格。
--x 在某些客户端里会被当成「减负 x」——因为 SQL 里 a--b 确实是「a 减去负 b」。
这个歧义是 SQL 语法留下的坑,加个空格就绕开了。
多行注释不能嵌套。 /* a /* b */ c */ 在第一个 */ 就结束了,
后面的 c */ 会变成语法错。
拼 SQL 字符串的时候特别容易踩——你注释掉一段带注释的代码,就出事了。
六、动手 Demo:把四类语句和三条规矩都跑一遍
这一段没有复杂查询,只验证三件事:四类语句分别是什么、关键字大小写真的不影响、注释真的被跳过。 规矩二(分号)在 SQLite 里演不出来——它的 Python 接口本来就不吃分号。 所以这一条只用一条不带分号的语句反证:分号可有可无,是工具的要求不是语法的要求。
import sqlite3
c = sqlite3.connect(":memory:")
# 四类语句各建一张小表登记,然后当数据查出来看
c.execute("create table kind (k text, full text, sample text)")
c.executemany("insert into kind values (?,?,?)", [
('DDL', '数据定义语言', 'create / alter / drop / truncate'),
('DML', '数据操纵语言', 'insert / update / delete / select'),
('DCL', '数据控制语言', 'grant / revoke'),
('TCL', '事务控制语言', 'commit / rollback / savepoint'),
])
for r in c.execute("select * from kind"):
print(r)
# 规矩一:关键字大小写都认,字符串里的值不认
c.execute("create table emp (ename text, job text)")
c.executemany("insert into emp values (?,?)",
[('SMITH', 'CLERK'), ('KING', 'PRESIDENT')])
print(c.execute("select ename from emp where job = 'CLERK'").fetchall())
print(c.execute("SELECT ENAME FROM EMP WHERE JOB = 'clerk'").fetchall())
# 规矩二:分号只是「说完了」的信号,不是语法的一部分
print(c.execute("select count(*) from emp").fetchall()) # 不带分号照样跑
# 规矩三:两种注释,多行注释不能嵌套
print(c.execute("select ename /* 块注释 */ from emp -- 行注释").fetchall())
实跑输出:
--------------------------------------------------------------------
第十五课:SQL 语言简介——四类语句,三条书写规矩
--------------------------------------------------------------------
**SQL 不是一种「语言风格」,是一套有编号的标准。**
**Oracle 的叫 SQL,MySQL 的叫 SQL,SQLite 的也叫 SQL**——
**共同的那部分叫标准 SQL,各家自己加的那部分叫方言。**
**本课后面所有的 SQL,都是「标准那部分尽量照标准、方言部分照 Oracle 来写」。**
四类语句:按「动了什么」分
--------------------------------------------------------------------
SQL> select kind, full, does, sample from sql_kind
KIND FULL DOES SAMPLE
--------------------------------------------------------------------------
DDL 数据定义语言 改结构:建、改、删对象 create / alter / drop / truncate
DML 数据操纵语言 改数据:增、删、改、查 insert / update / delete / select
DCL 数据控制语言 改权限:给权、收权 grant / revoke
TCL 事务控制语言 改事务边界:提交、回滚 commit / rollback / savepoint
已选择 4 行
**这条分界线不是学术分类,是操作后果的分级:**
**DDL 一执行就隐式提交**(不管你有没有 `commit`),**而且多数情况不能回滚**;
**DML 可以回滚**,只要还没 `commit`;**DCL 影响的是「谁能干什么」**,改错了别人立刻受影响;
**TCL 是你唯一能主动划边界的地方。**
**很多人栽在第一条上:**
**以为 `drop table` 之后还能 `rollback` 回来——不能。**
**它执行前就把你之前的事务默默提交了。**(这一点 13.1 节还会细讲)
第一条规矩:关键字不分大小写,数据分
--------------------------------------------------------------------
SQL> select ename, job from emp where job = 'CLERK' order by ename
ENAME JOB
------------
ADAMS CLERK
JAMES CLERK
MILLER CLERK
SMITH CLERK
已选择 4 行
SQL> SELECT ENAME, JOB FROM EMP WHERE JOB = 'clerk' ORDER BY ENAME
ENAME JOB
---------
已选择 0 行
**第一条小写关键字、大写数据,查出 4 行;第二条大写关键字、小写数据,查出 0 行。**
**结论:关键字(`select`/`from`/`where`)怎么写都认;字符串里的内容一个字母都不能错。**
**`'CLERK'` 和 `'clerk'` 是两个完全不同的值。**
**习惯上:关键字大写、表名列名小写**,这样一眼能分出「语法」和「你自己起的名」。
**Oracle 里表名和列名默认还会被转成大写存**,这一点 4.2 节会说。**
第二条规矩:语句用分号结尾
--------------------------------------------------------------------
**分号不是 SQL 的一部分,是「我说完了」的信号。**
**SQL*Plus 收到分号才把这一坨发给数据库。**
[Oracle 原码 · 本沙箱不执行]
| SQL> select ename
| 2 from emp
| 3 where deptno = 10;
|
| ENAME
| ----------
| CLARK
| KING
| MILLER
行号是 SQL*Plus 自己加的,不用你敲。
**一条语句可以随便折行,只要最后有分号。**
**反过来,忘了分号回车,光标会停在 `2 ` 等你——不是卡死,是等你把话说完。**
第三条规矩:注释有两种
--------------------------------------------------------------------
SQL> select style, syntax, scope from cmt
STYLE SYNTAX SCOPE
-------------------------------------------------------
单行注释 -- 一直管到本行末尾 临时屏蔽一行、给字段加说明
多行注释 /* 从这到那 */ 屏蔽一整段、写块级说明
已选择 2 行
SQL> select ename, sal /* 注释不影响执行 */ from emp where deptno = 10
ENAME SAL
-----------
CLARK 2450
KING 5000
MILLER 1300
已选择 3 行
**注释在 SQL 里是「被解析器跳过的文本」,不占执行时间。**
**但 `--` 后面必须有空格**(`--x` 在某些工具里会被当成减号),
**而且 `--` 只到本行末尾,下一行照常执行。**
**多行注释不能嵌套。** `/* a /* b */ c */` 在第一个 `*/` 就结束了,
**剩下的 `c */` 会变成语法错**——这个坑在拼 SQL 字符串时特别容易踩。
四类语句打出来是四行,sample 那一列就是每类的代表。
这张表本身是 DML 建起来、DML 查出来的——你看,SQL 能描述自己。
第一条查询(小写关键字、大写数据)查出 1 行;第二条(大写关键字、小写数据)查出 0 行。
差别只在字符串里的大小写,关键字全大写也没影响。
这就是「关键字不分大小写、数据分」最直接的证据。
不带分号的 select count(*) 照样跑通。
因为分号是 SQL*Plus 这种工具用来断句的,不是 SQL 引擎的要求。
把这条 SQL 粘进任何程序里,分号都是多余的(有些驱动甚至明确禁止)。
最后那条同时用了块注释和行注释,结果和没写注释一样。
注释是被解析器在词法阶段就丢掉的,不占执行时间、不影响执行计划。
Oracle 原码 里那些 grant、commit 本课都没法执行——SQLite 没有用户和权限体系,
也没有真正的 commit 语义(它是自动提交的)。这两类留在纸上讲,真跑要等有 Oracle 环境的时候。
30 秒实验
- 把
kind表里的 DDL 那行改成 DML,再查一次。 这张表只是记录,改它不影响任何真实行为——但它提醒你:文档和实现是两回事,别把注释当事实。 - 在
select ename from emp where job = 'CLERK'里把CLERK改成'CLERK '(末尾加个空格), 看还能不能查到。字符串比较是完全匹配,多余的空格也算不同。 - 把最后那条 SQL 的块注释去掉右半边
*/,跑一次看报什么错。 注释没闭合,解析器会把后面所有内容都当成注释,直到文件末尾。
常见误区
- 以为 SQL 是 Oracle 专有的。 SQL 是标准,Oracle 只是实现之一。
Oracle 多出来的那部分叫方言(
rownum、connect by、nvl),搬去 MySQL 就不认。 - 以为
drop table之后能rollback。 不能。 DDL 不写回滚日志,而且它在执行前会先把你之前的事务提交掉。 - 以为关键字必须大写。 大小写都认。 但养成统一大写的习惯,因为字符串和标识符混在一起时,大小写是唯一能帮你看清结构的东西。
- 用
--时不加空格。a--b会被解析成「a 减负 b」,结果是对的但意思完全变了, 而且你根本看不出来。
自测三问(点开对答案)
1. `truncate` 为什么算 DDL 而不是 DML? —— 因为它清空表的方式是**重建存储结构**,不是逐行删除。 **所以它不写回滚日志、不能带 `where`、执行即提交**——这三点都是 DDL 的特征。 2. DDL 的「隐式提交」会带来什么后果? —— 你之前攒着没提交的 DML 会被默默提交,**而且 `drop`/`truncate` 之后 `rollback` 救不回来。** **所以生产库做结构变更,只能靠备份,不能靠回滚。** 3. 为什么 `--` 后面必须加空格? —— 因为 SQL 里 `a--b` 是「a 减去负 b」的合法写法。 **不加空格,解析器会按减法算**,结果可能对,但意思完全变了。下一篇讲用户模式: Oracle 把「用户」和「一堆对象」绑在一起,那个容器就叫模式。 这也是「表明明建了却说找不到」的头号原因。
- ☐ (本篇完,下一篇 4.2 用户模式)
相关笔记
- 格式化查询结果
- 设置 SQL*Plus 的运行环境
-
- 🐬 MySQL必知必会/00-索引
-
- 🗄️ 数据库基础/00-索引
- 🏺 Oracle从入门到精通 · 索引