在Oracle的日常运维和开发调试中,我们经常需要执行一些只有个别条件不同的SQL,比如按不同的员工号查数据、按不同的日期范围做统计。如果每次都去改SQL文本,既麻烦又容易出错。SQL*Plus提供的define变量机制正好可以解决这个问题,它允许我们把会变化的值抽取成变量,在执行时再替换进SQL语句,从而把固定SQL变成可复用的参数化脚本。

什么是define变量,它是如何工作的
define变量是SQL*Plus提供的一种替换变量(substitution variable)机制。它的本质很简单:SQL*Plus在把SQL语句发送给Oracle服务器执行之前,会先扫描语句文本,一旦发现替换变量引用符号(即&符号),就把该变量名对应的字符串内容原样替换到SQL文本中,然后再交给数据库解析执行。
这个过程完全发生在客户端,Oracle服务器根本感知不到define变量的存在。理解这一点很重要:define变量不是绑定变量(bind variable),它做的是纯文本替换,因此同一个带define变量的SQL,替换后的文本不同,在数据库端就是不同的SQL,无法共享游标。这也是它和绑定变量在原理上的核心区别。
常见的define变量有三种来源:第一种是用DEFINE命令显式定义;第二种是使用单个&符号的替换变量,执行时SQL*Plus会提示你输入值;第三种是使用双&&符号,输入一次后变量会被自动保存下来,后续引用不再重复提问。下面我们逐一展开。
用DEFINE命令显式定义和引用变量
最基本的用法是通过DEFINE命令给变量赋值。语法是DEFINE 变量名 = 值,注意等号两边的空格是允许的,值会被当作字符串存储,但替换时是按原文替换,所以数字可以直接当数字用。
-- 定义变量 DEFINE dept_id = 20 DEFINE emp_name = SMITH -- 在SQL中引用,注意 & 符号 SELECT empno, ename, sal FROM emp WHERE deptno = &dept_id AND ename = '&emp_name'; -- 查看当前已定义的所有变量 DEFINE -- 查看某个变量 DEFINE dept_id
执行时SQL*Plus会先输出替换后的语句(可以用SET VERIFY OFF关闭这个回显),再交给数据库执行。如果要删除变量,使用UNDEFINE dept_id即可。用DEFINE定义的变量在当前会话内一直有效,直到执行UNDEFINE或者退出SQL*Plus,这一点和&&自动定义的变量行为一致。
需要特别注意值中的空格问题。如果定义时写成DEFINE emp_name = SMITH JONES,变量值就是整个SMITH JONES字符串,包括中间的空格,这在按名字查询时可能会导致查不到数据。遇到这类情况,建议用双引号明确包裹:DEFINE full_name = "SMITH JONES",替换时引号会一起进入SQL文本,正好符合字符串字面量的要求。
交互式输入:&与&&的区别
除了预先定义,也可以不定义直接在SQL中写&变量,执行时SQL*Plus会停下来提示输入。这种方式适合临时查询。例如:
SELECT * FROM emp WHERE empno = &emp_no;
执行后会提示“输入 emp_no 的值:”,输入7369回车即可。单&符号的特点是只问一次——同一条SQL内即使出现多个相同名字的单&变量,也只在解析这一条语句时各问一次,但下一次执行同一条SQL时还会再问。
而双&&符号的行为不同:第一次执行时提示输入,输入完成后SQL*Plus会自动用DEFINE把该变量保存下来,之后无论这条SQL再执行多少次,或者会话中其他SQL引用同名变量,都不会再提示,直接使用已保存的值。这在同一脚本中需要多处引用同一个值时非常方便:
SELECT &&report_date FROM dual;
SELECT * FROM sales_log WHERE log_date = TO_DATE('&&report_date','YYYY-MM-DD');
-- 第二条语句不会再次提示输入
UNDEFINE report_date
-- 删除后再次执行才会重新询问
如果你想让单&变量也具备“只问一次”的效果,可以开启SET CONCAT配合相关设置,但更直接的做法是在脚本开头统一DEFINE,结尾统一UNDEFINE,这样脚本的可读性和可维护性都更好。
几个实用技巧与常见坑
第一个技巧是控制替换行为的环境参数。SET DEFINE ON|OFF可以整体开关替换功能,当你的SQL文本本身包含&符号(比如拼接一个含&的字符串字面量)时,SQL*Plus会误把它当成变量引用而提示输入,此时临时SET DEFINE OFF再执行就能绕开。还可以用SET DEFINE x把替换符号改成其他字符,例如改成^,这样含&的文本就不会触发替换。
SET DEFINE ^ SELECT 'Tom & Jerry' AS nickname FROM dual; -- 此时 & 不会被当作变量提示 SET DEFINE &
第二个是转义。如果只想临时跳过某个&符号,可以用SET ESCAPE '\'设置转义符,然后在&前加反斜杠,写成\&,替换机制会忽略这个&,输出原文的&符号。这个方式比整体关闭DEFINE更精细。
第三个常见的坑是在拼接动态SQL时变量值包含单引号。因为define是纯文本替换,值里的单引号会直接进入SQL文本,可能破坏语法。遇到这种值,可以在输入时手工双写单引号,或者改用COLUMN定义的新值结合NEW_VALUE子句,也可以干脆改用PL/SQL的绑定变量,从根源上避开文本替换的脆弱性。
define变量与绑定变量的选择建议
既然define变量是文本替换,那么在高频执行的场景下它会带来硬解析问题:每次替换出的SQL文本不同,数据库无法复用执行计划。对比绑定变量,SQL文本固定、只有绑定值变化,游标可以共享,性能明显更好。
所以选择的原则很清晰:如果是交互式脚本、一次性运维任务、批量跑数脚本,执行次数有限,define变量简单直接,足够胜任;如果是应用程序中高频执行的SQL、循环中反复执行的语句,必须使用绑定变量。两者并不冲突,很多DBA的习惯是在SQL*Plus脚本里用define变量接收外部参数(比如配合&1这样的位置参数从命令行传入),脚本内部再通过PL/SQL绑定变量执行核心逻辑,兼顾灵活性与性能。
总的来说,define变量是SQL*Plus脚本化工作中绕不开的基础能力。掌握DEFINE、UNDEFINE、&与&&的差异,配合SET DEFINE、SET ESCAPE等参数处理特殊字符,就能写出参数清晰、可重复执行的运维脚本,避免手工改SQL带来的隐患。