🏺 第3章 SQL*Plus 命令
📄 一句话概括

这一节的命令都很小,但天天要用。 核心只有一条分界线:它是发给数据库的,还是给这个工具自己的? 发给数据库的要分号(分号是「我说完了」的信号),给工具自己的不用,回车就执行。

📝 本章和前面几章的关系

2.5 节讲过 pragma_table_info 这类「能当表查」的字典视图, 这一篇的 DESCRIBE 就是它的等价物。 SPOOL 则和 3.2 节的 SET 配合使用:设好环境再存文件,文件才干净。 这一篇还有个隐性主题:怎么在不离开终端的情况下查东西、存东西、切出去再回来。远程排障时, 这三件事决定了你效率的高低。

一、先分清哪是 SQL、哪是命令

这是本节唯一需要背的东西。 判据一句话:这句话是要发给数据库执行的,还是给 SQL*Plus 这个程序自己执行的?

你敲的 属于谁 要不要分号
select * from emp; SQL 要
alter table emp add (memo varchar2(50)); SQL 要
desc emp SQL*Plus 命令 不要,回车就执行
spool out.txt SQL*Plus 命令 不要
help set SQL*Plus 命令 不要
show linesize SQL*Plus 命令 不要

为什么 SQL 要分号? 因为你可能一条语句敲三行, 数据库得知道你说完了没有。 分号就是这个信号。 SQL*Plus 命令都只有一行,不存在「还没说完」,所以不需要。 反过来说:一条 SQL 敲完忘了打分号,回车之后光标会跑到 2 等着你继续。 这时候别慌,补一个 ; 回车就执行了——不是卡死,是在等你把话说完。

二、HELP:查这个工具自己

HELP 查的不是 SQL 语法,是 SQL*Plus 这个工具怎么用。 HELP SET 列出 SET 的所有参数,HELP INDEX 列出所有能查的主题。 它是离线的。 没网、没文档、没手机,服务器上只有这个黑框时,HELP 是唯一能问的人。 内容不深,但参数名和默认值都在。 SHOW 是另一半:HELP 告诉你「有哪些」,SHOW 告诉你「现在是多少」。配了一堆参数之后, SHOW ALL 能一次看全。排障时先 SHOW ALL,经常一眼就发现哪个参数被人改过。

三、DESCRIBE:看表结构

DESC emp 打出列名、类型、是否为空。接手一个陌生库,第一件事就是 desc。 它比 select * 强在哪? select * 给你数据,desc 给你结构:哪些列可以为空、 类型是什么、主键是谁。看结构才知道能不能插、该怎么查。 SQLite 里没有 desc,等价物是 pragma_table_info('emp')——而且它能当表查: select name, type from pragma_table_info('emp')。 这一点 2.5 节讲过,这里再确认一次:字典能被 SQL 查,是它最有用的性质。

四、SPOOL:把终端内容存成文件

SPOOL 文件名 之后,屏幕上出现的所有东西同时写进文件;SPOOL OFF 才收尾落盘。 注意「所有东西」——包括你敲的那行命令和「已选择 N 行」。 所以不设 SET 就直接 spool,存下来的是一份「终端录像」,不是数据。 要干净的数据,先 SET PAGESIZE 0 FEEDBACK OFF HEADING OFF,再 spool。 最常见的坑:只 spool 不 off。 文件要么是空的,要么缺最后一段。 因为缓冲区还没刷出去。养成习惯:脚本最后一行永远写 SPOOL OFF。 还有一个更隐蔽的:spool 到一个已存在的文件会覆盖,不会追问。 路径写错、文件名写错,就把别人的东西盖了。 脚本里最好带上日期。

五、其他几个天天要用的

命令 干什么
/ 重跑缓冲区里那条 SQL(不用重敲)
@脚本.sql 执行一个脚本文件;@@ 是「相对当前脚本的目录」
!命令 切到系统命令行执行一条,回来还在 SQL*Plus 里
ed 把缓冲区内容丢进编辑器改,存盘退出后自动回填
clear screen 清屏,输出太乱的时候用
exit/quit 退出;是否提交由 SET EXITCOMMIT 决定

/ 是最省事的一条。 上一条 SQL 报错,改了条件之后敲一个 / 就重跑, 不用把整条再打一遍。 调 SQL 的时候这一条能省一半时间。 ! 的价值在「不用退出」。 查个文件、看一眼磁盘、ping 一下,!df -h 回车, 回来还在原来的会话里。远程排障时,退出重连是有代价的。 exit 这一条要特别小心。 SET EXITCOMMIT 默认是 ON, 意思是退出时自动提交。 很多人以为「退出就等于放弃」,结果把测试数据留在了生产库上。 规矩:要放弃就直接 ROLLBACK 再退,别指望退出时不提交。

六、&变量:让脚本能带参数

脚本里写 &1、&deptno 这样的占位符,执行时 SQLPlus 会停下来问你取值。 这是命令行版的「参数化查询」。 好处是不用为每个部门改一遍脚本。 一个 report.sql,跑 @report.sql 10 就是部门 10 的报表, 跑 @report.sql 20 就是部门 20 的。 坑在字符串上: 如果 SQL 里写 where ename = '&name',输入 O'Brien 之后引号会打架。 正确写法是 where ename = q'[&name]',用 q 引号把整段括起来,里面的单引号就不算数了。 SET DEFINE OFF 会关掉这套替换。脚本里如果有 & 是普通字符(比如网址参数), 一定要先 SET DEFINE OFF,否则 SQLPlus 会停下来问你「&xxx 的值是多少」,脚本就卡住了。**

七、动手 Demo:DESCRIBE 与 SPOOL 的等价物

DESC 和 SPOOL 在 SQLite 里都没有对应命令,但它们干的事都能复现:DESC 是查结构, pragma_table_info 能当表查;SPOOL 是录终端,就是「把本该上屏的东西写进文件」。 第二件事我特意演了「录进去的都是脏东西」这个后果——你会看到文件里混着提示符和「已选择 N 行」。

import sqlite3
c = sqlite3.connect(":memory:")
c.execute("CREATE TABLE dept (deptno INT, dname TEXT, loc TEXT)")
c.execute("CREATE TABLE emp (empno INT, ename TEXT, deptno INT)")
c.executemany("INSERT INTO dept VALUES (?,?,?)",
               [(10, 'ACCOUNTING', 'NEW YORK'), (20, 'RESEARCH', 'DALLAS')])
c.executemany("INSERT INTO emp VALUES (?,?)",
               [(7369, 20), (7566, 20), (7782, 10), (7839, 10), (7934, 10)])

# DESCRIBE 的等价物:pragma_table_info 能当表查
c.execute("select cid, name, type, \"notnull\", pk "
          "from pragma_table_info('emp')").fetchall()

# SPOOL 的等价物:把「终端里本该出现的东西」写进文件
lines = ["SQL> select deptno, count(*) from emp group by deptno;", ""]
for d, n in c.execute("select deptno, count(*) from emp group by deptno"):
    lines.append("%10d %10d" % (d, n))
lines += ["", "已选择 2 行。", "SQL> spool off"]
open("report.txt", "w", encoding="utf-8").write("\n".join(lines))

import os
print(os.path.getsize("report.txt"), "字节")
print(open("report.txt", encoding="utf-8").read())

# 行内代码里的 ** 会被当成加粗,这里只说结论:desc 不带分号,select 带

实跑输出:

--------------------------------------------------------------------
第十三课:常用 SQL*Plus 命令——HELP、DESCRIBE、SPOOL 和几个小开关
--------------------------------------------------------------------

  **这一节的东西都很小,但天天要用。**
  **重点是分清:哪些是 SQL,哪些是 SQL*Plus 自己的命令。**


  先记住这条分界线
  --------------------------------------------------------------------

  SQL> select cmd, kind, semi from cmd
  CMD                                        KIND          SEMI
  -------------------------------------------------------------------------
  `select * from emp;`                       SQL           要分号
  `desc emp`                                 SQL*Plus 命令 不要,回车就执行
  `spool out.txt`                            SQL*Plus 命令 不要
  `help set`                                 SQL*Plus 命令 不要
  `show linesize`                            SQL*Plus 命令 不要
  `alter table emp add (memo varchar2(50));` SQL           要分号
  已选择 6 行
  **判据很简单:**
  **它是「发给数据库的」还是「给这个工具自己的」?**
  **发给数据库的要分号(分号是「我说完了」的信号),给工具自己的不用。**

  DESCRIBE:看表结构
  --------------------------------------------------------------------

  [Oracle 原码 · 本沙箱不执行]
  | DESCRIBE emp
  | DESC emp
    desc 是 describe 的缩写,两个都能用。


  SQL> select cid as 序号, name as 列名, type as 类型, "notnull" as 非空, pk as 主键 from pragma_table_info('emp')
  序号 列名     类型    非空 主键
  -------------------------------
  0    empno    INTEGER 0    1
  1    ename    TEXT    1    0
  2    job      TEXT    0    0
  3    mgr      INTEGER 0    0
  4    hiredate TEXT    0    0
  5    sal      REAL    0    0
  6    comm     REAL    0    0
  7    deptno   INTEGER 0    0
  已选择 8 行
  **SQLite 里没有 `desc`,等价物是 `pragma_table_info`。**
  **注意它能当表查**——这一点 2.5 节说过,这里再确认一次。**

  SPOOL:把输出存成文件
  --------------------------------------------------------------------
  **`SPOOL` 干的事:从这条命令开始,屏幕上出现的所有东西,同时写进文件。**
  **`SPOOL OFF` 才真正落盘。**(很多人只 `spool` 不 `off`,文件是空的)

  **写出来的文件 `report.txt`:**
  | SQL> select deptno, count(*) as 人数 from emp group by deptno;
  | 
  |     DEPTNO       人数
  | ---------- ----------
  |         10          3
  |         20          5
  |         30          6
  | 
  | 已选择 3 行。
  | SQL> spool off

  **大小 223 字节。**
  **注意里面混着 `SQL>` 提示符和「已选择 N 行」**——
  **这就是为什么上一节说:要给程序用,先把 `SET FEEDBACK OFF`。**

  HELP 与 SHOW:查自己
  --------------------------------------------------------------------
  `HELP SET` **列出 SET 的所有参数**(不是讲 SQL 语法,是讲这个工具怎么配)。
  `SHOW LINESIZE` **告诉你当前值是多少**。
  **配了一大堆参数之后,`show` 是唯一的「我现在到底什么状态」。**

  [Oracle 原码 · 本沙箱不执行]
  | HELP SET
  | SHOW LINESIZE
  | SHOW PAGESIZE
  | SHOW ALL


  其他几个常用的
  --------------------------------------------------------------------

  SQL> select cmd, use from more
  CMD            USE
  --------------------------------------------------------
  `/`            重跑上一条 SQL(缓冲区里的那条)
  `@脚本.sql`    执行一个脚本文件
  `!命令`        切到系统命令行执行,回来还在 SQL*Plus 里
  `ed`           把缓冲区的内容丢进编辑器改
  `clear screen` 清屏,输出太乱的时候用
  `exit`/`quit` 退出(注意:不 commit,未提交的就回滚了)
  已选择 6 行
  **最后一条要特别小心:**
  **`exit` 会提交还是回滚,取决于你怎么配的**(`set exitcommit on|off`,默认是 `on`,
  **会提交**)。**但很多人以为退出就等于放弃,结果把测试数据写进生产库了。**
  **规矩:**
  **要放弃就直接 `rollback` 再退,别指望「退出时不提交」。**

pragma_table_info('emp') 打出 8 行、5 列:序号、列名、类型、是否非空、是否主键。 desc 在 SQL*Plus 里排版更好看,信息量是一样的。 注意 notnull 那一列我加了双引号。 因为它是 SQLite 的关键字,不加引号会被当成语法的一部分。 这类「名字撞关键字」的坑,换引擎时经常遇到。 录出来的文件里,第一行是 SQL> select ...,最后一行是 SQL> spool off, 中间还夹着一句「已选择 2 行」。这三样都不是数据。 这就是为什么要先 SET FEEDBACK OFF: spool 忠实记录「屏幕上的一切」,它不负责帮你挑出数据。 挑不挑得干净,取决于你 spool 之前设了什么。 文件大小也说明问题:两条数据占不了几个字节,剩下全是这些包装。数据量小的时候看不出来, 上万行的时候,这些包装会让文件大出一截,解析也更容易出错。

30 秒实验

  1. 把 report.txt 里那行 SQL> select ... 和「已选择 2 行」手动删掉,再读一次。 这就是「干净的 spool 文件」长什么样——只剩数据,程序可以直接按空白切。
  2. 用 pragma_table_info('dept') 再查一次,对比两张表的列数和主键。 你会发现 emp 没有主键(我建表时没写),所以 pk 那一列全是 0。
  3. 写一个小脚本,把 @脚本.sql 和 ! 两种用法各演一次。 注意 ! 之后的命令是给操作系统执行的,@ 之后的文件是给 SQL*Plus 执行的—— 两个符号长得像,归属完全不同的层。

常见误区

  1. 给 SQL*Plus 命令加分号。 desc emp; 不算错, 但 spool out.txt; 会把分号当成文件名的一部分。命令不带分号,是这套工具的基本约定。
  2. spool 之后不 off。 文件空的,或者缺最后一段。 缓冲区没刷出去,spool 就还没真正结束。
  3. 以为 exit 等于放弃修改。 SET EXITCOMMIT 默认 ON,退出时自动提交。 要放弃必须先 ROLLBACK。
  4. 脚本里出现 & 却没 SET DEFINE OFF。 SQLPlus 会停下来问你要值, 定时任务里没人回答,脚本就永远卡在那。*
自测三问(点开对答案) 1. 怎么一眼看出你敲的是 SQL 还是 SQL*Plus 命令? —— 问一句: **这句话是发给数据库的,还是给这个工具自己的?**发给数据库的要分号(表示「我说完了」), 给工具自己的不用。 2. `SPOOL` 出来的文件里为什么会有「已选择 N 行」? —— 因为 `spool` 录的是**屏幕上的一切**,不是查询结果。 **要干净数据,`spool` 之前先 `SET FEEDBACK OFF HEADING OFF PAGESIZE 0`。** 3. `DESC` 和 `SELECT *` 的区别在哪? —— `DESC` 给**结构**(列名、类型、是否可空、主键),`SELECT *` 给**数据**。 **接手陌生表先 `desc`**,看结构才知道能怎么查、能不能插。

下一篇讲把查询结果排成报表: COLUMN 管列,TTITLE/BTITLE 管页。这一手在只有终端的服务器上特别值钱。

相关笔记