数据库只管给数据,怎么排得好看是客户端的事。
SQLPlus 给了两件工具:COLUMN 管列(宽度、表头、对齐、数字格式),
TTITLE/BTITLE 管页(页眉、页脚)。
这一手的价值不在「好看」,在服务器上只有终端的时候,它是唯一能把结果输出成人能看的样子*。
3.2 节的 SET 是全局的(整张表怎么摆),这一节的 COLUMN 是逐列的(这一列怎么摆)。
两者配合,才拼得出一张像样的报表。
数字格式串这套东西,4.4 节讲 TO_CHAR 时会再出现一次。
它们本来就是同一套模板语言——在这里学会,到那里就只是换了个函数名。
一、COLUMN:一列一列地调
SET LINESIZE 是「整行多宽」,COLUMN 是「这一列多宽、表头叫什么、怎么对齐、数字怎么排」。
粒度完全不同。
| 参数 | 作用 | 例子 |
|---|---|---|
HEADING |
改表头文字 | HEADING '姓名' |
FORMAT |
定宽度和数字格式 | FORMAT a10 / FORMAT 999,990.00 |
JUSTIFY |
左中右对齐 | JUSTIFY CENTER |
NOPRINT |
这一列不打印 | NOPRINT |
NULL |
空值显示成什么 | NULL '无' |
a10 里的 a 是「字符串(alpha)、宽 10」。 数字用另一套:
999,990.00 表示「最多六位整数、带千分位、两位小数」。
NOPRINT 值得单独提一句: 有时候你按某列排序,但不想把它打出来,
COLUMN deptno NOPRINT 就能让它在结果里隐身,排序照样生效。
二、数字格式串:9 与 0 的区别
这两个字符的区别只有一句话:9 是「有数就显示、没数留空」,0 是「没数就补零」。
FORMAT 999990 打 2975 得到 2975;FORMAT 099999 得到 002975。
补零在编号、流水号上很有用,在金额上就是灾难——没人想看到 00800.00 元。
FM 前缀去掉前导空格。 Oracle 的数字格式默认按位补齐,短数字前面会留一串空格用来右对齐;
FM999990 就把这些空格去掉。导数据时几乎一定要加 FM,否则每个字段前面都是一堆空格。
TO_CHAR(2975, '999,990.00') 用的是同一套格式串,结果一模一样。
区别只在位置:COLUMN 是「显示时排」,TO_CHAR 是「取出来就是字符串」。
后者会把结果变成文本,排序规则也跟着变——这一点 4.4 节会细讲。
三、TTITLE 与 BTITLE:给每页加头尾
TTITLE 是页眉,BTITLE 是页脚,都在顶部或底部打固定文字。
三个位置:LEFT、CENTER、RIGHT。
多行用 - 续行(这不是减号,是 SQLPlus 的续行符),
SKIP 1 是「空一行」。 拼起来就是一张带抬头、带页码的报表。
SQL.PNO 是内置变量,值就是当前页码。
所以 BTITLE CENTER '第 ' SQL.PNO ' 页' 会真的打出「第 1 页」「第 2 页」。
这才是 PAGESIZE 存在的意义:不分页,页码就没意义。
注意 TTITLE 是「每页都打」。 设一次,后面所有输出都带着它,
直到 TTITLE OFF。* 很多人设完忘了关,后面查什么都顶着那个抬头,很尴尬。
四、BREAK ON:分组断行
BREAK ON deptno SKIP 1 的意思是:按 deptno 分组,值一变就空一行。
报表里「一个部门一段」的效果就是这么来的。
配合 COMPUTE 还能打小计:
BREAK ON deptno SKIP 1 加 COMPUTE SUM OF sal ON deptno,每个部门末尾会多一行合计。
但 BREAK 有个前提:数据必须按那一列排好序。没 ORDER BY 的话,deptno 会 10、30、
10 地来回跳,BREAK 就断得莫名其妙。
顺序永远是先 ORDER BY,再 BREAK。
五、为什么现在很少人这么写
因为格式属于展示层。 把「列宽 12、千分位、页码」写进 SQL 脚本,
等于把展示逻辑焊死在数据层上。
换一个客户端,这些设置全都不认。 网页、Excel、报表工具,没有一家会去读你的 COLUMN 设置。
所以现代做法是:SQL 只负责取数据,排版交给客户端。
但在运维场景下它依然值钱。 服务器上只有终端,没有图形界面,
spool 加 column 是唯一能把结果输出成人能看的样子。
会这一手,远程排障时能直接把结果贴进邮件,不用来回导。
我的建议是:知道它、会用、但别往生产脚本里塞。
排查问题时临时设一设,导出数据时清干净——这两件事分清楚就行。
六、动手 Demo:把一组查询排成一张报表
这一段把 COLUMN 和 TTITLE 的效果整块演出来:
列宽固定、表头改名、金额带千分位、页眉居中、页码右对齐。
然后用同一组格式串,对照 9 与 0 的差别。注意这张表里的每个数字,都是从库里查出来现算的。
import sqlite3
c = sqlite3.connect(":memory:")
c.execute("CREATE TABLE emp (ename TEXT, sal REAL, hiredate TEXT, deptno INT)")
c.executemany("INSERT INTO emp VALUES (?,?,?,?)", [
('SCOTT', 3000.0, '1987-04-19', 20),
('FORD', 3000.0, '1981-12-03', 20),
('JONES', 2975.0, '1981-04-02', 20),
('ADAMS', 1100.0, '1987-05-23', 20),
('SMITH', 800.0, '1980-12-17', 20),
])
# COLUMN ename HEADING '姓名' FORMAT a10 的等价物:定宽 + 改表头
# COLUMN sal HEADING '月薪' FORMAT 999,990.00 的等价物:千分位 + 两位小数
W = 40
print('RESEARCH 部门薪资表'.center(W))
print('=' * W)
print('姓名'.ljust(14) + '月薪'.ljust(14) + '入职日期'.ljust(12))
print('-' * W)
for ename, sal, hd in c.execute("select ename, sal, hiredate from emp "
"where deptno = 20 order by sal desc"):
print(ename.ljust(14) + format(sal, ',.2f').ljust(14) + hd.ljust(12))
print('-' * W)
print('第 1 页'.rjust(W)) # BTITLE CENTER '第 ' SQL.PNO ' 页'
# 同一组格式串,换成 Python 的格式写法对照
for fmt, v in [('999,990.00', 2975.0), ('999990', 2975.0), ('0999.9', 2975.0)]:
print(fmt, '->', v)
实跑输出:
--------------------------------------------------------------------
第十四课:格式化查询结果——COLUMN 与 TTITLE/BTITLE
--------------------------------------------------------------------
**这一节是「把查询变成报表」。**
**数据库只管给数据,怎么排得好看是客户端的事。**
**SQL*Plus 给了两个工具:`COLUMN` 管列(宽度、表头、对齐、格式),`TTITLE`/`BTITLE` 管页(页眉、页脚)。**
COLUMN:一列一列地调
--------------------------------------------------------------------
[Oracle 原码 · 本沙箱不执行]
| COLUMN ename HEADING '姓名' FORMAT a10
| COLUMN sal HEADING '月薪' FORMAT 999,990.00
| COLUMN hiredate HEADING '入职日期' FORMAT a10
**三个参数最常用:**
**`HEADING` 改表头文字、`FORMAT` 定宽度和数字格式、`JUSTIFY` 定对齐。**
**`a10` 里的 `a` 是「字符串、宽 10」;`999,990.00` 是「数字、带千分位、两位小数」。**
RESEARCH 部门薪资表
========================================
姓名 月薪 入职日期
----------------------------------------
SCOTT 3,000.00 1987-04-19
FORD 3,000.00 1981-12-03
JONES 2,975.00 1981-04-02
ADAMS 1,100.00 1987-05-23
SMITH 800.00 1980-12-17
----------------------------------------
第 1 页
**上面这张表就是 `COLUMN` 加 `TTITLE` 的产物:**
**列宽固定、表头改名、金额带千分位、页眉页脚居中。**
数字格式差在哪
--------------------------------------------------------------------
**同一个数,三种格式:**
`FORMAT 999,990.00` → **2,975.00**
`FORMAT 999990` → **2975**
`FORMAT 0999.9` → **2975.0**
**`9` 是「有数就显示、没数留空」,`0` 是「没数就补零」,`FM` 去掉前导空格。**
**`TO_CHAR` 用的是同一套格式串**(4.4 节会再讲一次)——**它们是同一件事:把数字按模板排版。**
TTITLE 与 BTITLE:给每一页加头尾
--------------------------------------------------------------------
[Oracle 原码 · 本沙箱不执行]
| TTITLE LEFT '企业人事系统' -
| CENTER '员工薪资明细' -
| RIGHT '2026-10-30' -
| SKIP 1
| BTITLE CENTER '第 ' SQL.PNO ' 页'
| COLUMN ename HEADING '姓名' FORMAT a10
| COLUMN sal HEADING '月薪' FORMAT 999,990.00
| BREAK ON deptno SKIP 1
**`SQL.PNO` 是「当前页码」,`SKIP 1` 是「空一行」,`BREAK ON` 是「按某列分组,变了就空一行」。**
**这一套在 SQL*Plus 里能拼出相当像样的报表**——**但只在终端里。**
**要 PDF、要图表,得靠 EM 或者第三方报表工具。**
为什么现在很少人这么写
--------------------------------------------------------------------
**因为格式属于展示层。**
**把「列宽 12、千分位、页码」写进 SQL 脚本,等于把展示逻辑焊死在数据层。**
**换一个客户端(比如网页),这些设置全都不认。**
**所以现代做法是:SQL 只负责取数据,排版交给客户端。**
**但运维场景下它很有用:**
**服务器上只有终端,没有图形界面,`spool` 加 `column` 是唯一能把结果输出成人能看的样子。**
**会这一手,远程排障时省很多事。**
这张表就是 column 加 ttitle 的产物: 列宽固定、表头改成中文、金额带千分位、页眉居中、
页脚右对齐。数据库一点都没参与排版。
注意 3,000.00 和 2,975.00 的宽度一样。 因为 FORMAT 定死了宽度,
数字不会把列撑开——这正是报表要的效果:上下对得齐。
800.00 前面没有 0,也没有被补成 000800.00。因为 999,990.00 里全是 9,
9 的规矩是「没数就留空」。
换成 0999,990.00,它立刻变成 000800.00。
页脚那个「第 1 页」是右对齐的。 对应 BTITLE RIGHT 或者 SQL.PNO 的位置——
页码右对齐是印刷报表的惯例,因为翻页时手指在右下角。
这里只有五条数据,看不出分页。 但 PAGESIZE 一设成个位数,这份报表就会自己翻页,
每页都重打抬头和页码——这就是 TTITLE 加 SQL.PNO 的价值。
30 秒实验
- 把
FORMAT 999,990.00换成FORMAT 0999,990.00,看800.00变成什么。 这就是9与0的唯一区别:一个留空,一个补零。 - 给表加一列
comm(全是空值),再COLUMN comm NULL '无'。 空值会显示成「无」,而不是一片空白——报表里空白和零是两件事,必须能分清。 - 同时设
TTITLE和BREAK ON deptno SKIP 1,把两个部门的数据混在一起查。 注意BREAK要生效,必须先ORDER BY deptno——不排序,它会断得毫无规律。
常见误区
- 以为
COLUMN改的是数据。 它只改显示。sal存的是2975,3,000.00是渲染出来的样子,取出来算还是原值。 - 忘了
TTITLE OFF。 设一次之后每份输出都顶着那个抬头,直到你关掉它或者退出会话。 - 不
ORDER BY就BREAK ON。 数据没排序,分组边界就是乱的, 断行位置毫无意义,小计也跟着错。 - 把报表格式写进生产脚本。 换客户端就不认, 而且把展示逻辑和数据逻辑搅在一起,以后谁都不敢改。 排查时临时用,交付时清干净。
自测三问(点开对答案)
1. `FORMAT 999990` 和 `FORMAT 099999` 打同一个数,差在哪? —— **`9` 是「没数就留空」,`0` 是「没数就补零」。**2975 用前者得 `2975`,用后者得 `002975`。 **金额上用 `9`,流水号上用 `0`。** 2. `TTITLE` 里的 `SQL.PNO` 是什么?为什么它需要 `PAGESIZE` 配合? —— 它是**当前页码**这个内置变量。 **不分页就没有「页」的概念,页码也就没有意义**——所以 `PAGESIZE` 设成 0 时,页码永远是 1。 3. `BREAK ON` 为什么必须先排序? —— 因为它是**按「值变了」来断行的**。 数据没排序,同一个值会分散在各处,**分组边界就变成随机的**——断行位置乱,小计也跟着错。第 3 章到此为止。
下一篇进第 4 章:SELECT 基础——从最简单的 select * from emp 开始,把查询这件事拆开看。
- ☐ (本篇完,下一篇 4.1 SQL 语言简介)
相关笔记
- 设置 SQL*Plus 的运行环境
- 常用 SQL*Plus 命令
- 逻辑存储结构
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引