PL/SQL 的变量必须先声明后使用。语法是「名字 类型 [:= 初值]」,赋值用 :=,= 是比较。
最值得养成的习惯是 %TYPE:让变量类型跟着表列走,表结构改了你一行代码都不用动。
5.1 节把块的骨架搭好了,这一篇往里填「变量」。
后面游标、过程、函数全都要声明变量,所以这一篇是硬基础。
%TYPE 和 %ROWTYPE 这两个写法,Oracle 特有,别的库基本没有——
它们是 PL/SQL 最实用的一处方言。
一、基本类型:四种够用九成
NUMBER 数字、VARCHAR2 变长字符串、DATE 日期时间、BOOLEAN 布尔。
这四个覆盖日常绝大多数场景。
| 类型 | 含义 | 要留意什么 |
|---|---|---|
NUMBER(p,s) |
总长 p 位、小数 s 位 | 不写 p、s 就是浮点,能存很大但会丢精度 |
VARCHAR2(n) |
变长字符串,最长 n 字节 | SQL 里上限 4000,PL/SQL 里能到 32767 |
DATE |
日期加时间,精确到秒 | 没有「只有日期」的类型,时分秒默认 0 |
BOOLEAN |
真/假/空 | 只能给 PL/SQL 用,不能存进表 |
BOOLEAN 存不进表,这一条最容易栽。
表里要存真假,一般用 CHAR(1) 存 'Y'/'N',或者 NUMBER(1) 存 1/0。
这是 Oracle 的历史遗留——SQL 标准里一直没定布尔类型,Oracle 干脆把它限制在 PL/SQL 里。
23c 才加了 BOOLEAN 列类型,教材讲的 19c 还没有。
VARCHAR2 里的 2 不是版本号。
它是「第 2 代」,因为第一代 VARCHAR 的行为有争议(空串算不算 NULL),
Oracle 用 VARCHAR2 把语义定死了,并且承诺以后不再改。
所以写 VARCHAR2,别写 VARCHAR——后者行为将来可能变。
二、精度是硬限制:NUMBER(p,s) 怎么算
NUMBER(7,2) 是「总共 7 位有效数字,其中 2 位小数」。
所以整数部分最多 5 位,最大能存 99999.99。
超了会怎样,分两种:
小数位超了是「悄悄四舍五入」(123.456 变成 123.46,不报错);
总位数超了才是「溢出」(999999.99 报 ORA-01438)。
这两种行为在报表上长得都很正常。
金额字段的精度一定要一次定对——
定小了,某天一笔大额进来直接报错;定大了,索引变大、比较变慢。
经验值:金额用 NUMBER(12,2) 起步(能存到百亿级),科学计算用不带精度的 NUMBER(浮点,
范围大但会丢精度),计数用 NUMBER(10) 或 INTEGER。
三、%TYPE 与 %ROWTYPE:跟着表走
v_sal emp.sal%TYPE 的意思是「v_sal 的类型跟 emp.sal 一模一样」。
编译时 Oracle 去数据字典里查一下,把类型抄过来。
好处就一个字:不写死。
你写 v_sal NUMBER(7,2),哪天 DBA 把 sal 改成 NUMBER(9,2),你的代码就溢出;
写 v_sal emp.sal%TYPE,代码一个字不用动。
v_row emp%ROWTYPE 是「一整行」,v_row.ename、v_row.sal 都能直接取。
它就是一个结构体,配合游标 FETCH ... INTO v_row 特别顺手。
注意 %TYPE 不带 NOT NULL 约束——它只抄类型,不抄约束。
所以 emp.ename 是 NOT NULL,v_name emp.ename%TYPE 仍然可以是 NULL。
想连约束一起要,得写 NOT NULL := 初值。
四、常量:CONSTANT 必须当场给值
v_pi CONSTANT NUMBER := 3.14159;——声明常量,就必须在声明时赋值。
这是唯一一处「声明即初始化」强制成立的地方。
后面再赋值是编译错,不是运行时错。
这个区别很大:编译错在「部署之前」就发现了,运行时错要等「跑到那一行」。
所以常量该用就用。
它是「这条规矩由编译器帮你守」,而注释只是「请你自己记得」。
能用编译器守的,别交给记性。
五、表达式:日期也能算
PL/SQL 的表达式基本就是 SQL 的表达式,加减乘除、比较、逻辑,都一样。唯一要留意的是日期。
日期加一个数,加的是「天」。
SYSDATE + 1 是明天,SYSDATE + 1/24 是一小时后,SYSDATE + 1/1440 是一分钟后。
因为日期在 Oracle 里就是一个数,小数部分就是时分秒。
所以「加一个月」不能用 +30。
月份长度不一样,1 月 31 日加 30 天得 3 月 2 日,
而 ADD_MONTHS 会给你 2 月 28 日——那才是「加一个月」的本意。
还有一个坑:NULL 参与任何运算,结果还是 NULL。
v_x := NULL; v_y := v_x + 1; 之后 v_y 是 NULL,不是 1。
要兜底只能用 NVL。
六、动手 Demo:算一遍精度,看一眼 %TYPE 的取数来源
这一段做三件事:按 NUMBER(7,2) 的规则判四个数能不能存下、
把 emp 表的列类型读出来(%TYPE 就是从这里抄的)、验证日期加减的单位是天。
精度判断我是真按规则算的:整数位数比 p - s 大就溢出,小数位数比 s 大就舍入。
# NUMBER(7,2) 能放多大:整数 5 位、小数 2 位
def fit(v, p=7, s=2):
ip, _, dp = v.partition('.')
ip = ip.lstrip('0') or '0'
if len(ip) > p - s:
return '溢出'
if len(dp) > s:
return '舍入成 %.2f' % round(float(v), s)
return '放得下'
for v in ['12345.67', '999999.99', '123.456', '0.005']:
print(v, '->', fit(v))
# %TYPE 的意思:照着表里那一列的类型来
import sqlite3
c = sqlite3.connect(':memory:')
c.execute('create table emp (empno int, ename text, sal real)')
print(c.execute("select name, type from pragma_table_info('emp')").fetchall())
# CONSTANT:Python 只能靠约定,PL/SQL 是编译期报错
PI = 3.14159
PI = 3.14 # 没有任何语言级拦截
print('PI =', PI)
# 日期就是数:+1 是明天,+1/24 是一小时后
import datetime
now = datetime.datetime(2026, 10, 30, 9, 0, 0)
print(now + datetime.timedelta(days=1))
print(now + datetime.timedelta(hours=1))
print(now + datetime.timedelta(minutes=1))
实跑输出:
--------------------------------------------------------------------
第二十二课:数据类型、变量和常量——赋值是 :=,不是 =
--------------------------------------------------------------------
**PL/SQL 的变量必须先声明、后使用。**
**声明写在 `DECLARE` 段,语法是「名字 类型 [:= 初值]」。**
**记住这一点:赋值用 `:=`,`=` 是比较。** **这一条和 SQL 完全不同**——SQL 里 `=` 是比较,
赋值在 `update` 里。 **混用会得到一个「语法错误」,而且报错位置常常让你误以为是别的地方。**
基本类型:四种够用九成
--------------------------------------------------------------------
SQL> select name, means, note from typ
NAME MEANS NOTE
-------------------------------------------------------------------------------------------
NUMBER(p,s) 数字,总长 p 位、小数 s 位 不写 p、s 就是浮点,能存很大但会丢精度
VARCHAR2(n) 变长字符串,最长 n 字节 n 默认按字节算;SQL 里上限 4000,PL/SQL 里能到 32767
DATE 日期加时间,精确到秒 没有「只有日期」的类型,时分秒默认为 0
BOOLEAN 真/假/空 只能给 PL/SQL 用,不能存进表
已选择 4 行
**`BOOLEAN` 值得单独提一句:它只能活在 PL/SQL 里,存不进表。**
**表里要存真假,一般用 `CHAR(1)` 存 `'Y'`/`'N'`,或者 `NUMBER(1)` 存 1/0。**
**这是 Oracle 一个历史遗留的设计,很多新手在这上面卡过一次。**
NUMBER(7,2):精度是硬限制
--------------------------------------------------------------------
**`NUMBER(7,2)` 的意思是「总共 7 位有效数字,其中 2 位是小数」。** **所以整数部分最多 5 位**,
最大能存 99999.99。
这四个数放进去会怎样:
**12345.67** → 存得下(整数 5 位、小数 2 位,正好放得下)
**999999.99** → 溢出(整数部分 6 位,超过 5 位上限)
**123.456** → 存得下(小数位 3 超过 2,舍入成 123.46)
**0.005** → 存得下(小数位 3 超过 2,舍入成 0.01)
**注意 `123.456` 那一行:它不报错,是悄悄四舍五入成 123.46。**
**超出小数位是「舍入」,超出总位数才是「溢出」——两种处理完全不同。**
**这两种行为在报表上长得都很正常,所以金额字段的精度一定要一次定对。**
%TYPE 与 %ROWTYPE:跟着表走
--------------------------------------------------------------------
**`v_sal emp.sal%TYPE` 的意思是「`v_sal` 的类型跟 `emp.sal` 一模一样」。**
**好处是表结构变了,变量自动跟着变,不用改代码。**
**`v_row emp%ROWTYPE` 是「一整行」**,`v_row.ename`、`v_row.sal` 都能直接取。
**它就是一个结构体。**
**这台机器的 `emp` 表长这样(`%TYPE` 就是从这里取的):**
SQL> select name as 列名, typ as 类型, nn as 非空 from colinfo
列名 类型 非空
---------------------
empno INTEGER 否
ename TEXT 是
job TEXT 否
mgr INTEGER 否
hiredate TEXT 否
sal REAL 否
comm REAL 否
deptno INTEGER 否
已选择 8 行
**`%TYPE` 的真正价值在「不写死」。**
**你写 `v_sal NUMBER(7,2)`,哪天 DBA 把 `sal` 改成 `NUMBER(9,2)`,你的代码就溢出。**
**写 `v_sal emp.sal%TYPE`,代码一个字不用动。** **这是 PL/SQL 里最值得养成的一个习惯。**
常量:CONSTANT 必须当场给值
--------------------------------------------------------------------
**`v_pi CONSTANT NUMBER := 3.14159;`——声明时常量,就必须在声明时赋值。**
**后面再赋值就是编译错**,不是运行时错。
**Python 里这种「改常量」根本没有语言级的拦截,只能靠约定。** **PL/SQL 是在编译期就报错**,
这就是强类型语言和脚本语言的一处硬差别。
**所以常量该用就用**——它是「这条规矩由编译器帮你守」, **而注释只是「请你自己记得」。**
表达式:日期也能算
--------------------------------------------------------------------
**PL/SQL 的表达式基本就是 SQL 的表达式**,加减乘除、比较、逻辑,都一样。
**唯一要留意的是日期:日期加一个数,加的是「天」。**
**`SYSDATE + 1` 是明天,`SYSDATE + 1/24` 是一小时后,`SYSDATE + 1/1440` 是一分钟后。**
**因为日期在 Oracle 里就是一个数(第几天),小数部分就是时分秒。**
**所以「加一个月」不能用 `+30`**——月份长度不一样。**要用 `ADD_MONTHS`。**
四个数里只有一个溢出:999999.99。
它整数部分 6 位,超过 7 - 2 = 5 位的上限。
其余三个都「放得下」,其中两个是被舍入过的。
123.456 变成 123.46,0.005 变成 0.01——都没报错。
这就是「小数位超了只是舍入」的直接证据。
在 Oracle 里它连警告都不给,数据就这么悄悄变了。
pragma_table_info('emp') 打出八列的类型。
emp.sal%TYPE 编译时就是来这儿查的——查到 REAL,就把变量定成对应的数字类型。
这就是「跟着表走」的机制。
注意这台机器上 sal 的类型是 REAL,Oracle 里是 NUMBER(7,2)。
类型名不同,但「%TYPE 会去查字典」这个机制是一样的。
常量那一段是反例:Python 里 PI = 3.14 直接把原来的值覆盖了,什么都没拦。
PL/SQL 里同样的代码是编译错。
这是强类型语言和脚本语言最直观的一处差别。
日期那三行:加 1 天、加 1 小时、加 1 分钟。
注意 1 小时写的是 hours=1,在 PL/SQL 里要写成 +1/24——
因为单位是「天」,所以任何时间间隔都要除以当天的份数。
30 秒实验
- 把
fit的p、s改成(5,2),再跑那四个数。 这次12345.67也应该溢出了——整数部分 5 位,正好卡在5 - 2 = 3的上限外面。 - 加一个数
0.0001试试。 小数位 4 位,舍入成0.00——这就是「钱算着算着变成 0」的来源:精度定得太小。 - 用
pragma_table_info('dept')查一下deptno的类型,再对比emp.deptno。 两张表的deptno类型必须一致,否则连接时会做隐式转换,索引就废了。 - 把
datetime.timedelta(days=1)换成timedelta(days=30),对比「加 30 天」和「加一个月」。 2 月那一个月会让你看到差别。
常见误区
- 用
=赋值。 PL/SQL 里=是比较,赋值是:=。 写错会报语法错,而且报错位置常常指在别的地方。 - 以为
NUMBER(7,2)能存 7 位整数。 是「总共 7 位,其中 2 位小数」, 整数只剩 5 位。 金额字段定小了,早晚有一天会溢出。 - 以为
%TYPE会连NOT NULL一起抄过来。 不会。它只抄类型,不抄约束。 - 日期加一个月写
+30。 月份长度不一样。加月一律用ADD_MONTHS,它自己处理月末。
自测三问(点开对答案)
1. `NUMBER(7,2)` 最大能存多少?`123.456` 放进去会怎样? —— 最大 **99999.99**(整数 5 位、小数 2 位)。 `123.456` **不报错,悄悄舍入成 123.46**——小数位超了只舍入,总位数超了才溢出。 2. `%TYPE` 到底省了什么? —— 省了「把类型写死」。**表列类型一变,变量自动跟着变,代码不用改。** **注意它只抄类型,不抄 `NOT NULL` 约束。** 3. 为什么「加一个月」不能用 `+30`? —— 因为月份长度不一样。 **1 月 31 日加 30 天是 3 月 2 日,而 `ADD_MONTHS` 给的是 2 月 28 日**—— 后者才是「加一个月」的本意。下一篇讲流程控制:
两个选择(IF、CASE),三个循环(LOOP、WHILE、FOR)。
重点会放在「哪个至少跑一次」和「FOR 的范围是闭区间」这两处。
- ☐ (本篇完,下一篇 5.3 流程控制语句)
相关笔记
- PL/SQL 简介
- Oracle 常用系统函数
- 用户模式
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引