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

SET 系列命令只改一件事: 输出长什么样。一行数据都不动。但它决定了你在终端里能不能把结果看清楚。 最常改的四个: LINESIZE 一行多宽、PAGESIZE 一页几行、FEEDBACK 要不要报行数、HEADING 要不要表头。

📝 本章和前面几章的关系

上一篇讲的是「话怎么递过去」,这一篇讲「回来的东西怎么摆」。 两件事完全无关:换一个客户端,这些设置全都不认,但数据一个字节不差。 这里的每个开关,后面写脚本、导数据时都会再用到。 尤其是 PAGESIZE 0 这一条,不知道它的人经常花半天去解析「已选择 N 行」。

一、SET 改的全是「皮」

先把边界划清楚:SET 是 SQL*Plus 这个客户端自己的命令,不是 SQL。它不发给数据库, 数据库根本不知道你设了什么。 所以它不带分号(带了也不算错,但没必要),而且一退出就没了——下次进来又是默认值。 要长期生效,得写进 login.sql 或 glogin.sql。 这四个默认值先记住: LINESIZE 默认 80,PAGESIZE 默认 14,FEEDBACK 默认 ON,HEADING 默认 ON。 80 列是上古终端的宽度,拿到今天的数据上,一定不够。

二、LINESIZE:一行多宽

SET LINESIZE 200 之后,一行最多放 200 个字符。 超了不会横向滚动,是直接折到下一行——这是最容易误判的地方:你以为表被切碎了,其实是折行了。 默认 80 遇上六列表格,最后一列必然被甩到下一行去。更糟的是折行之后表头和数据行错位, 看着像两列数字对不上。 上限是 32767(老版本 2000)。写脚本时直接拉满,不用心疼——反正只在输出时起作用,不占内存。

三、PAGESIZE:一页几行

PAGESIZE 是「一屏多少行」,每翻一页 SQL*Plus 都会重新打一遍表头。 人在终端里看,这个设计是对的:翻过去还能知道每列是什么。 但输出到文件时它就是垃圾。 一份一万行的数据,文件里会夹着几百行重复表头和一串空行。 所以导数据第一件事:SET PAGESIZE 0。 PAGESIZE 0 的意思是「不分页」,不是「一页零行」。 这类「0 表示关闭」的约定在 SQL*Plus 里很常见,别按字面理解。

四、FEEDBACK 与 HEADING:要不要那些话

已选择 14 行。 这句话是 FEEDBACK 打的,不是查询结果的一部分。 SET FEEDBACK OFF 之后,这条提示消失,但数据一行不少。 SET HEADING OFF 去掉表头。两个一起关,输出就变成纯数据——这正是程序想要的样子。 但有个坑:FEEDBACK 关掉之后,你就不知道一条 update 改了几行。 交互式排障时不要关它,update 打出「已更新 0 行」往往就是答案。 同一个开关,导数据时要关,查问题时必须开。

五、给程序吃的数据,一套固定模板

记住这一行,能省很多事: SET PAGESIZE 0 FEEDBACK OFF HEADING OFF LINESIZE 32767 TRIMSPOOL ON TRIMSPOOL ON 去掉行尾空格,否则每个字段后面都是一串空格,split 出来一堆空值。 这一条教材没细讲,但不加会很难受。 再加一句 SET TERMOUT OFF(只写文件不上屏),导十万行的时候屏幕就不会卡死。 TERMOUT 是「终端输出」的总闸,SPOOL 照样工作。

六、还有几个一设就见效的

SET TIMING ON:每条 SQL 后面打出耗时。调优第一步不是看执行计划,是先知道哪条慢。 SET SERVEROUTPUT ON:不开这个,dbms_output.put_line 打出来的东西你一个字都看不到。 这是新手最常见的「明明执行了却没输出」。 SET AUTOTRACE ON:附上执行计划和统计信息。 要先用 plustrce.sql 建 plustrace 角色,否则报权限错。 它和 TIMING 一起开,是命令行调优的标准姿势。

七、动手 Demo:同一份数据,四种渲染

我写了个小函数,按 LINESIZE 真的把行折断、按 PAGESIZE 真的分页。 数据完全不动,只改渲染参数,你看输出差多少。 四组对照:linesize 40 折成半截、linesize 120 完整、pagesize 5 分页、feedback 与 heading 全关。

import sqlite3
c = sqlite3.connect(":memory:")
c.execute("CREATE TABLE emp (empno INT, ename TEXT, job TEXT, "
          "hiredate TEXT, sal REAL, deptno INT)")
c.executemany("INSERT INTO emp VALUES (?,?,?,?,?,?)", [
    (7369, 'SMITH', 'CLERK', '1980-12-17', 800.0, 20),
    (7499, 'ALLEN', 'SALESMAN', '1981-02-20', 1600.0, 30),
    (7566, 'JONES', 'MANAGER', '1981-04-02', 2975.0, 20),
    (7839, 'KING', 'PRESIDENT', '1981-11-17', 5000.0, 10),
    (7934, 'MILLER', 'CLERK', '1982-01-23', 1300.0, 10),
])

rows = c.execute("select empno, ename, job, hiredate, sal, deptno "
                 "from emp order by empno").fetchall()

def render(rows, linesize, feedback=True, heading=True):
    """按 linesize 折断,超宽就截断并打 ~ 标记。"""
    cols = ["EMPNO", "ENAME", "JOB", "HIREDATE", "SAL", "DEPTNO"]
    widths = []
    for i, name in enumerate(cols):
        w = max([len(name)] + [len(str(r[i])) for r in rows]) + 1
        widths.append(w)
    def fit(line):
        if len(line) <= linesize:
            return [line]
        return [line[:linesize - 1] + "~"]
    out = []
    if heading:
        out += fit("".join(n.ljust(w) for n, w in zip(cols, widths)).rstrip())
        out.append("-" * linesize)
    for r in rows:
        out += fit("".join(str(v).ljust(w)
                           for v, w in zip(r, widths)).rstrip())
    if feedback:
        out += ["", "已选择 %d 行。" % len(rows)]
    return out

for ln in render(rows, 40):   print(ln)   # 太窄,最后一列没了
print('=' * 50)
for ln in render(rows, 120):  print(ln)   # 够宽,六列都在
print('=' * 50)
for ln in render(rows, 120, feedback=False, heading=False):
    print(ln)                             # 给程序吃的干净数据

实跑输出:

--------------------------------------------------------------------
第十二课:设置 SQL*Plus 的运行环境——SET 命令改的全是「皮」
--------------------------------------------------------------------

  **`SET` 系列命令只改「输出长什么样」,一行数据都不动。**
  **但它决定了你在终端里能不能看清结果。**
  **教材里最常见的那几个:** `LINESIZE`(一行多宽)、`PAGESIZE`(一页几行)、
  `FEEDBACK`(要不要打「已选择 N 行」)、`HEADING`(要不要表头)。


  先把「一行多宽」这件事演出来
  --------------------------------------------------------------------

  [Oracle 原码 · 本沙箱不执行]
  | SET LINESIZE 200
  | SET PAGESIZE 50
  | SET FEEDBACK ON
  | SET HEADING ON

  **`LINESIZE` 太小,一行会被折断**——**SQL*Plus 不是横向滚动,是直接换行,看起来就像表被切碎了。**

  linesize 40:宽表被折成两截
  --------------------------------------------------------------------
  EMPNO ENAME  JOB       HIREDATE   SAL  ~
  ----------------------------------------
  7369  SMITH  CLERK     1980-12-17 800.0~
  7499  ALLEN  SALESMAN  1981-02-20 1600.~
  7521  WARD   SALESMAN  1981-02-22 1250.~
  7566  JONES  MANAGER   1981-04-02 2975.~
  7654  MARTIN SALESMAN  1981-09-28 1250.~
  7698  BLAKE  MANAGER   1981-05-01 2850.~
  7782  CLARK  MANAGER   1981-06-09 2450.~
  7788  SCOTT  ANALYST   1987-04-19 3000.~
  7839  KING   PRESIDENT 1981-11-17 5000.~
  7844  TURNER SALESMAN  1981-09-08 1500.~
  7876  ADAMS  CLERK     1987-05-23 1100.~
  7900  JAMES  CLERK     1981-12-03 950.0~
  7902  FORD   ANALYST   1981-12-03 3000.~
  7934  MILLER CLERK     1982-01-23 1300.~

  已选择 14 行。

  **行尾那个 `~` 是截断标记**(真 SQL*Plus 不标,直接换行,更难读)。
  **看出问题了吗:最后一列 `DEPTNO` 整列没了,`SAL` 顶到了本该属于它的位置上;`1300.0` 还被砍成 `1300.`。**
  **一张六列的表,看上去像五列——而且没人告诉你少了一列。**

  linesize 120:一列都不折
  --------------------------------------------------------------------
  EMPNO ENAME  JOB       HIREDATE   SAL    DEPTNO
  ------------------------------------------------
  7369  SMITH  CLERK     1980-12-17 800.0  20
  7499  ALLEN  SALESMAN  1981-02-20 1600.0 30
  7521  WARD   SALESMAN  1981-02-22 1250.0 30
  7566  JONES  MANAGER   1981-04-02 2975.0 20
  7654  MARTIN SALESMAN  1981-09-28 1250.0 30
  7698  BLAKE  MANAGER   1981-05-01 2850.0 30
  7782  CLARK  MANAGER   1981-06-09 2450.0 10
  7788  SCOTT  ANALYST   1987-04-19 3000.0 20
  7839  KING   PRESIDENT 1981-11-17 5000.0 10
  7844  TURNER SALESMAN  1981-09-08 1500.0 30
  7876  ADAMS  CLERK     1987-05-23 1100.0 20
  7900  JAMES  CLERK     1981-12-03 950.0  30
  7902  FORD   ANALYST   1981-12-03 3000.0 20
  7934  MILLER CLERK     1982-01-23 1300.0 10

  已选择 14 行。

  **同一份数据,只是把 `LINESIZE` 从 40 改到 120。**
  **这就是「环境设置」的全部意义:它不改数据,只决定你能不能看清。**

  pagesize 5:分成三页,每页都重复表头
  --------------------------------------------------------------------
  EMPNO ENAME  JOB       HIREDATE   SAL    DEPTNO
  ------------------------------------------------
  7369  SMITH  CLERK     1980-12-17 800.0  20
  7499  ALLEN  SALESMAN  1981-02-20 1600.0 30
  7521  WARD   SALESMAN  1981-02-22 1250.0 30
  7566  JONES  MANAGER   1981-04-02 2975.0 20
  7654  MARTIN SALESMAN  1981-09-28 1250.0 30

    ---- 第 2 页 ----

  EMPNO ENAME  JOB       HIREDATE   SAL    DEPTNO
  ------------------------------------------------
  7698  BLAKE  MANAGER   1981-05-01 2850.0 30
  7782  CLARK  MANAGER   1981-06-09 2450.0 10
  7788  SCOTT  ANALYST   1987-04-19 3000.0 20
  7839  KING   PRESIDENT 1981-11-17 5000.0 10
  7844  TURNER SALESMAN  1981-09-08 1500.0 30

    ---- 第 3 页 ----

  EMPNO ENAME  JOB       HIREDATE   SAL    DEPTNO
  ------------------------------------------------
  7876  ADAMS  CLERK     1987-05-23 1100.0 20
  7900  JAMES  CLERK     1981-12-03 950.0  30
  7902  FORD   ANALYST   1981-12-03 3000.0 20
  7934  MILLER CLERK     1982-01-23 1300.0 10

  已选择 14 行。

  **`PAGESIZE` 是「一屏几行」**,
  翻页时 SQL*Plus 会**重新打表头**——
  **所以你会看到表头反复出现。**
  **输出到文件时,很多人忘了设 `PAGESIZE 0`,结果文件里全是重复表头和空行。**

  FEEDBACK 与 HEADING
  --------------------------------------------------------------------
  7369  SMITH CLERK    1980-12-17 800.0  20
  7499  ALLEN SALESMAN 1981-02-20 1600.0 30
  7521  WARD  SALESMAN 1981-02-22 1250.0 30

  **`SET FEEDBACK OFF` 去掉「已选择 N 行」,`SET HEADING OFF` 去掉表头。**
  **这两个开关在「生成数据文件」时特别有用**——**你要的是干净的数据,不是一张给人看的表。**

  **一条经验:任何要交给程序处理的数据,一律 `SET PAGESIZE 0 FEEDBACK OFF HEADING OFF LINESIZE 32767`。**  **否则你会花半天去解析那些「已选择 N 行」。**

linesize 40 那一组,最后一列 DEPTNO 整列没了,SAL 顶到了本该属于它的位置上, 1300.0 还被砍成 1300.。 一张六列的表看上去像五列,而且没有任何提示。这就是默认 80 在宽表上的真实后果。 行尾那个 ~ 是我加的截断标记。真 SQL*Plus 不标,直接折行——所以真环境里更难发现少了东西。 它给不了你这个提示,只能你自己数列。 linesize 120 那一组什么都没变,只是宽了。 同一份数据、同一条 SQL,唯一区别是渲染参数——这就是「环境设置不改数据」最直白的证据。 pagesize 5 那一组,表头出现了三次。 这是分页的正确行为(人翻屏要看表头),但对文件就是灾难。 导数据不设 PAGESIZE 0,文件里就会长这样。 最后一组把 FEEDBACK 和 HEADING 都关了,输出只剩数据行。 注意列之间还是对齐的(空格还在),只是没有表头告诉你每列是什么。 这就是程序想要的:它不需要知道列名,它按位置取。

30 秒实验

  1. 把 linesize 改成 60 再跑一次,找到「刚好不折」的那个临界宽度。 做法是把六列的实际宽度加起来:六列的宽度加一之和,就是临界值。
  2. 给 render 加一个 pagesize=0 的分支,确认输出里没有 ---- 第 N 页 ----。 PAGESIZE 0 是关闭分页,不是「一页零行」——这个坑要亲手踩一次才记得住。
  3. 把 feedback=False 改成只关 HEADING、保留 FEEDBACK,看输出变成什么样。 这时候你能知道有几行,但不知道每列是什么。

常见误区

  1. 以为 SET LINESIZE 能改变数据。 它只影响显示。 你把 LINESIZE 设成 10,数据一个字节没变,只是每条都折成一堆碎片。
  2. 以为 SET 设完就永久生效。 它是会话级的,退出就没了。 要长期生效得写进 login.sql,或者干脆写在脚本开头。
  3. 导数据时不设 PAGESIZE 0。 结果文件里全是重复表头和「已选择 N 行」, 再用脚本去解析,就得写一堆过滤逻辑——本来一行设置能解决的事。
  4. 把 SET SERVEROUTPUT ON 忘了。 PL/SQL 里 dbms_output.put_line 打了半天, 屏幕上一个字没有,不是没执行,是没开这个开关。
自测三问(点开对答案) 1. `SET LINESIZE 40` 之后,表看起来少了一列,数据丢了吗? —— 没丢。**折行或截断只发生在显示层**,`select` 出来的还是那么多列。 **换一个宽点的终端,或者把 `LINESIZE` 调大,列就回来了。** 2. 导数据给程序用,必须设哪几个参数? —— **`PAGESIZE 0`(关分页)、`FEEDBACK OFF`(关行数提示)、`HEADING OFF`(关表头)、** **`LINESIZE 32767`(放宽)、`TRIMSPOOL ON`(去行尾空格)。**数据量大再加 `TERMOUT OFF`。 3. 为什么交互排障时不能关 `FEEDBACK`? —— 因为它会告诉你「已更新几行」。 **`update` 打出「已更新 0 行」,往往直接说明条件写错了**——关掉它,这个最重要的线索就没了。

下一篇讲常用命令: HELP、DESCRIBE、SPOOL,还有 /、@、! 这几个小开关。重点是分清哪些是 SQL、 哪些是工具自己的命令。

相关笔记