在 APEX 页面中,页面项、应用项、报表列、内置替换字符串和交互式网格列值会出现在 HTML 表达式、模板指令、SQL、PL/SQL、验证、动态操作和工作流活动里。可靠做法不是记住一种万能语法,而是先判断当前上下文:HTML 渲染用替换语法,SQL/PLSQL 用绑定变量或 APEX_SESSION_STATE 类型化读取,多值项先拆分再参与查询。

4 在页面中引用数据值#

开发页面时,经常要把页面项或报表列的值用于显示格式、查询条件和业务逻辑。本章按使用位置拆解这些引用方式,帮助你避免三类常见错误:HTML 输出未正确转义、SQL/PLSQL 中把字符串误当数字或日期、Ajax 动态操作没有提交或返回所需项值。

学习本章前,建议准备一个包含 EMPDEPT 或等价演示数据的 APEX 应用,并能进入 Page Designer 修改区域属性、列属性、验证和动态操作。

4.1 在 HTML 表达式中使用数据值#

在 Cards、Content Row、Template Components、Interactive Grid 等区域的 HTML 表达式里,页面项、应用项、内置替换字符串和区域数据源列通常使用 &NAME. 形式引用。语法以与号开头、以点号结束。例如,Content Row 区域基于 DEPT 表时,可以在 Overline 槽位中同时显示部门名和地点:

<strong>&DNAME.</strong> → &LOC.

在 Classic Report、Interactive Report 或自定义模板中,列值一般使用 #COLUMN_NAME#。如果只想把 LOC 用作 DNAME 列的辅助显示,可以把 LOC 列类型设为 Hidden,再在 DNAME 列的 HTML Expression 中引用:

<strong>#DNAME#</strong> → #LOC#

当值进入 HTML 属性时,要追加 !ATTR,让 APEX 按属性上下文转义:

<a href="#TARGET_LINK!ATTR#"><strong>#DNAME#</strong></a>

当列值本身已经包含可信 HTML,并且你确实要让浏览器按 HTML 解释时,才使用 !RAW

<i>#DESCRIPTION_IN_HTML!RAW#</i>

复现路径#

  • 前提:有一个基于 DEPT 的 Content Row 或 Classic Report 区域,至少包含 DNAMELOC
  • 位置:Page Designer → 区域或列 → HTML Expression
  • 动作:在 Content Row 中使用 &DNAME.,在报表列中使用 #DNAME#;在链接属性中使用 !ATTR
  • 验证点:运行页面,确认显示内容正确,隐藏列不额外占用报表列,链接属性中的特殊字符不会破坏 HTML。
  • 预期结果:同一个数据值根据上下文采用不同引用语法,页面显示稳定且不会意外输出未转义 HTML。

4.2 在模板指令中使用数据值#

模板指令(Template Directives)可以在 HTML 表达式里做条件输出、分支匹配、循环分隔值和应用模板。指令花括号中的变量名只写名称,不加 &# 或结尾点号;指令内部实际输出值时,再按当前区域上下文选择 &NAME.#COLUMN_NAME#

在 Cards、Content Row、Template Components、Interactive Grid 中,如果只在 TITLE 有值时输出标题,可写为:

{if TITLE/}&TITLE.{endif/}

在 Classic Report、Interactive Report 或自定义模板里,列值使用 #COLUMN_NAME#。闭合斜线前允许有空格:

{if TITLE /}#TITLE#{endif/}

模板指令适合把多列组合成更自然的结果。例如 PERSONS 表有 HONORIFICFIRST_NAMELAST_NAME,邮件模板可以这样输出称呼:有头衔时显示 “Dear Professor Smith,”,没有头衔时显示 “Dear Jennifer,”。

Dear {if HONORIFIC/}#HONORIFIC# #LAST_NAME#{else/}#FIRST_NAME#{endif/},

如果 Interactive Report 的 ACTIVITIES.TAGS 列保存冒号分隔的标签,可以用 {loop/} 把每个标签显示为一个 Chip:

{loop ":" TAGS/}<span class="a-Chip">&APEX$ITEM.</span>{endloop/}
图 4-1:使用 {loop/} 指令把标签格式化为 Chip。
表 4-1 常用模板指令
用途 模板指令
NAME 有值时输出内容,也可用 {else/} 处理无值分支。
{if NAME/} ...
{else/} ...
{endif/}
NAME 的值匹配多个 {when/} 分支,也可用 {otherwise/} 处理未命中情况。
{case NAME/}
  {when X/} ...
  {when Y/} ...
  {otherwise/} ...
{endcase/}
按分隔符拆分 NAME,循环中用 &APEX$ITEM.#APEX$ITEM# 取当前值,用 &APEX$I.#APEX$I# 取序号。
{loop "," NAME/}
  ... &APEX$ITEM. ... &APEX$I. ...
{endloop/}
把属性值传给模板,再应用指定模板。属性值仍要按上下文使用 #NAME#&NAME.
{with/}
   ATTR1:=value1
   ATTR2:=#NAME#
{apply TEMPLATENAME/}

复现路径#

  • 前提:准备一个报表列,例如 TAGS,值形如 Bug:Feature:Docs
  • 位置:Page Designer → 报表列 → HTML Expression
  • 动作:使用 {loop ":" TAGS/} 遍历标签,并用 APEX$ITEM 输出当前标签。
  • 验证点:运行页面,确认每个分隔值成为独立 Chip;尝试空值,确认不会出现多余标记。
  • 预期结果:模板指令在不写额外 SQL 的情况下完成条件格式和列表格式化。

4.3 把指令参考放进 Dock#

Universal Theme Reference 应用包含各类模板指令的示例。访问并收藏 oracleapex.com/ut 后,可以把它安装为 Progressive Web App(PWA),这样就能像本地应用一样从 Dock 或任务栏打开。该应用的 Reference 区域中有 Template Directives 页面,列出指令示例和受支持选项。

图 4-2:作为 PWA 使用的 Universal Theme Reference 模板指令参考。

复现路径#

  • 前提:浏览器允许安装 PWA。
  • 位置:Universal Theme ReferenceReferenceTemplate Directives
  • 动作:安装 PWA 或添加书签;查找 ifcaseloopwith/apply 示例。
  • 验证点:能从 Dock 或任务栏直接打开参考应用,并能找到当前项目所需指令示例。
  • 预期结果:调试模板表达式时不必离开开发上下文去搜索语法。

4.4 使用上下文相关的 Help 选项卡#

Page Designer 中的 Help 选项卡与 Layout 默认位于同一组标签页。选中 Property Editor 中的任意属性后,Help 会说明该属性支持哪些替换语法、是否允许模板指令,并在某些条目中给出示例。在 App Builder 的其他编辑页面中,也可以点击字段旁边的帮助图标查看类似说明。

图 4-3:Help 选项卡提供与当前属性相关的帮助。

复现路径#

  • 前提:打开一个页面并进入 Page Designer。
  • 位置:Property Editor 中选择 HTML ExpressionSQL Query 或动态操作相关属性,再查看 Help
  • 动作:确认该属性支持的替换字符串、模板指令和示例。
  • 验证点:不要把某个区域支持的语法直接套到另一个属性;以 Help 中当前属性说明为准。
  • 预期结果:减少语法位置用错导致的运行时错误。

4.5 在 SQL 和 PL/SQL 中使用数据值#

在 SQL 和 PL/SQL 中引用页面值时,要根据上下文选择 :NAME 绑定变量,或使用 APEX_SESSION_STATE 包中的 GET_TYPE() 系列函数。可引用的数据包括页面项、应用项,以及 APP_USERAPP_IDAPP_PAGE_ID 等内置替换字符串名称。

一般规则是:写在 APEX 页面或组件定义中的 SQL/PLSQL,用绑定变量最直接;写在数据库命名程序单元中的通用业务逻辑,应优先通过参数传值;需要按名称读取会话状态并要求类型正确时,使用 APEX_SESSION_STATE.GET_NUMBERGET_DATEGET_TIMESTAMPGET_VARCHAR2

4.5.1 使用页面项和应用项数据值#

在直接写入 APEX 页面和组件定义的 SQL/PLSQL 中,用 :NAME 绑定变量按名称引用页面项或应用项。常见位置包括区域 SQL 查询、WHERE 条件、本地或共享组件 LOV、验证、页面处理、工作流活动、服务器端条件表达式,以及 Execute Server-side Code 类型的动态操作。

只要不是 DDL 语句,SQL 或 PL/SQL 中能放表达式的位置通常都能使用绑定变量。在 PL/SQL 中,还可以用 := 给页面项或应用项赋值。例如页面 5 的业务逻辑需要把 P5_JOBCLERK 改成 ANALYST

-- Change clerk to analyst
IF :P5_JOB = 'CLERK' THEN
   :P5_JOB := 'ANALYST';
END IF;

复现路径#

  • 前提:页面上有 P5_JOB 项,运行时可能为 CLERK
  • 位置:Page Designer → Processing → Process,或 Dynamic Actions → Execute Server-side Code
  • 动作:写入上方 PL/SQL;如果通过 Ajax 动态操作触发,还要在 Items to Submit 中填入 P5_JOB,必要时在 Items to Return 中返回 P5_JOB
  • 验证点:提交或触发动作后,确认 P5_JOB 的服务器端值已更新。
  • 预期结果:绑定变量既可读也可写,页面业务逻辑能按项值分支执行。

4.5.2 使用列数据值#

当 SQL 或 PL/SQL 正在处理 Interactive Grid 的行时,除了页面项和应用项,也可以用 :COLUMN_NAME 引用当前行的列值。典型场景包括网格列验证、Editable Region 指向网格的页面处理、以及响应网格列值变化的 Execute Server-side Code 动态操作。

在动态操作场景里,引用的列名要写入 Items to SubmitItems to Return;属性值是一个或多个列名的逗号分隔列表,不写前导冒号。

处理新增或修改的网格行时,也可以通过给 :COLUMN_NAME 赋值来修改当前行列值:

-- Change clerk to analyst in current grid row being processed
IF :JOB = 'CLERK' THEN
   :JOB := 'ANALYST';
END IF;

复现路径#

  • 前提:页面有 Editable Interactive Grid,包含 JOB 列。
  • 位置:Interactive Grid 的列验证、Editable Region 处理,或列 Change 动态操作。
  • 动作:使用 :JOB 读取或赋值;Ajax 动态操作中把 JOB 写入提交/返回属性。
  • 验证点:修改当前行 JOB 后保存或触发动态操作,确认只影响当前行。
  • 预期结果:当前行列值能参与服务器端逻辑,且客户端与服务器值保持同步。

4.5.3 绑定值默认按字符串处理#

SQL 和 PL/SQL 中的绑定变量默认都按字符串处理。例外很少:Oracle Database 26ai 中可显式把页面项数据类型设为 BOOLEAN;另外 Hidden、Text Area、Rich Text Editor 可选择 CLOB,Switch 和 Checkbox 可选择 BOOLEAN。即使页面项是 Date PickerNumber Field:P5_START_DATE:P5_MAX_QUANTITY 这类绑定仍会以 VARCHAR2 字符串形式进入数据库表达式。

这个规则在简单比较里可能被数据库隐式转换掩盖,但在 CASE、验证表达式、日期格式掩码不一致、多值列表等场景中会暴露为运行时错误或逻辑错误。

4.5.3.1 把数据值作为数字处理#

如果页面项包含数字值,在数据库能自动做类型转换的场景下,把它作为 VARCHAR2 绑定变量使用通常可以工作。例如:

QUANTITY BETWEEN 1 AND :P5_MAX_QUANTITY

但在 CASE 表达式等要求类型一致的位置,字符串绑定变量可能导致错误:

QUANTITY BETWEEN 1 AND CASE CATEGORY
                          WHEN 'SMALL' THEN 10
                          WHEN 'LARGE' THEN :P5_MAX_QUANTITY
                          ELSE              15
                       END
ORA-00932: expression (:1) is of data type CHAR,
           which is incompatible with expected data type NUMBER

解决办法是用 APEX_SESSION_STATE.GET_NUMBER() 按数字读取。它会考虑页面项上配置的格式掩码,并把值转换为数字结果。

另一个常见错误是比较两个 Number Field 页面项。下面的查询示例页包含 P1_SAL_RANGE_LOWP1_SAL_RANGE_HIGH

图 4-4:使用两个 Number Field 页面项输入数字范围。

如果验证 Check Salary Range 直接比较两个绑定变量,用户输入低值 22、高值 3 时不会得到预期错误,因为字符串比较认为 22 小于 3

:P1_SAL_RANGE_LOW <= :P1_SAL_RANGE_HIGH

应改为:

apex_session_state.get_number('P1_SAL_RANGE_LOW')
 <= apex_session_state.get_number('P1_SAL_RANGE_HIGH')
图 4-5:按数字处理页面项后,错误范围触发预期验证消息。

复现路径#

  • 前提:页面有两个 Number Field:P1_SAL_RANGE_LOWP1_SAL_RANGE_HIGH
  • 位置:Page Designer → Validations → PL/SQL Expression。
  • 动作:先用绑定变量直接比较复现问题,再改为 APEX_SESSION_STATE.GET_NUMBER
  • 验证点:输入低值 22、高值 3 并提交。
  • 预期结果:修正后显示 “Salary range low value must be less than or equal to high value.” 或等价验证错误。

4.5.3.2 把数据值作为日期处理#

页面项保存日期时,如果直接用 VARCHAR2 绑定变量参与日期比较,而页面项格式掩码与应用默认短日期格式不一致,就可能报错。例如 Date Picker 页面项用于以下条件:

HIREDATE < :P34_HIRED_BEFORE

如果该页面项使用 DD-MON-YYYY 格式掩码,而应用默认短日期格式为当前区域设置的 DS,运行时可能出现:

ORA-01858: A non-numeric character was found instead of a numeric character.

解决办法是按日期或时间戳读取值,把页面项名称传给 GET_TIMESTAMP()

HIREDATE < apex_session_state.get_timestamp('P34_HIRED_BEFORE')

它会根据页面项配置的格式掩码完成转换。忘记这一步不仅会报错,也可能让日期比较得到意外结果。

图 4-6:使用两个 Date Picker 页面项输入日期范围。

例如验证 Check Hiredate Range 直接比较两个 Date Picker 绑定变量时,用户输入开始日期 22-MAY-2025、结束日期 30-APR-2025,可能不会收到预期错误,因为比较发生在字符串顺序上:

:P1_HIREDATE_RANGE_START <= :P1_HIREDATE_RANGE_END

应改为按时间戳比较:

apex_session_state.get_timestamp('P1_HIREDATE_RANGE_START')
 <= apex_session_state.get_timestamp('P1_HIREDATE_RANGE_END')
图 4-7:按日期处理页面项后,错误日期范围触发预期验证消息。

复现路径#

  • 前提:页面有两个 Date Picker:P1_HIREDATE_RANGE_STARTP1_HIREDATE_RANGE_END
  • 位置:Page Designer → Validations → PL/SQL Expression。
  • 动作:先用绑定变量直接比较复现问题,再改为 APEX_SESSION_STATE.GET_TIMESTAMP
  • 验证点:输入开始日期晚于结束日期,例如 22-MAY-202530-APR-2025
  • 预期结果:修正后显示 “Hiredate Range ends before it starts.” 或等价验证错误。

4.5.3.3 使用分隔值#

当页面项包含多个值,并用冒号 :、逗号 , 或其他字符分隔时,可以在 SQL 或 PL/SQL 中使用这些多值,但不能把整个字符串当成 SQL 的多个行值。正确做法是先拆分,再把拆分结果作为表表达式或 PL/SQL 集合处理。

4.5.3.3.1 使用分隔的数字值#

处理分隔的数字值时,使用 APEX_STRING.SPLIT_NUMBERS()。假设 P5_SELECTED_EMPNOSSelect Many 页面项,分隔符为冒号;LOV 以 ENAME 为显示值、EMPNO 为返回值。用户选择多个员工后,页面项值可能是 7839:7369:7654

下面这种写法很诱人,但在多值时有问题:

select empno, ename, sal
  from emp
 where empno in (:P5_SELECTED_EMPNOS) /* Tempting, but problematic */

当页面项包含多个冒号分隔的数字时,运行时可能报错:

ORA-01722: unable to convert string value containing ':' to a number:
ORA-03302: (ORA-01722 details) invalid string value: 7839:7369:7654

应把字符串拆成独立数字,并从单列表结果中选择 COLUMN_VALUE

select empno, ename, sal
  from emp
 where empno in (select column_value
                   from apex_string.split_numbers(:P5_SELECTED_EMPNOS,':'))

在 PL/SQL 中,可以用 APEX 内置数字列表类型 APEX_T_NUMBER 接收结果:

declare
   l_selected_empnos apex_t_number;
begin
   l_selected_empnos := apex_string.split_numbers(:P5_SELECTED_EMPNOS,':');
   for j in 1..l_selected_empnos.count loop
      -- Use the j-th employee number here
      -- l_selected_empnos(j)
   end loop;
end;

复现路径#

  • 前提:创建 Select Many 页面项 P5_SELECTED_EMPNOS,返回员工编号,分隔符为 :
  • 位置:报表区域 SQL 查询或服务器端 PL/SQL 处理。
  • 动作:先用 IN (:P5_SELECTED_EMPNOS) 观察失败,再改用 APEX_STRING.SPLIT_NUMBERS
  • 验证点:选择多个员工,确认查询返回多个员工行且没有数字转换错误。
  • 预期结果:每个员工编号被作为单独数字参与过滤。

4.5.3.3.2 使用分隔的字符串值#

处理分隔的字符串值时,使用 APEX_STRING.SPLIT()。假设 P5_SELECTED_SIZESSelect Many 页面项,分隔符为竖线 |;LOV 以 SIZE_DESCRIPTION 为显示值、SIZE_CODE 为返回值。用户选择多个尺码后,页面项值可能是 SM|MD|XXL

下面这种写法不会按多个尺码过滤,它只会查找字面值等于 SM|MD|XXL 的行,因此通常返回空结果:

select id, sku, price
  from items
 where item_size in (:P5_SELECTED_SIZES) /* Tempting, but problematic */

正确做法是指定分隔符并拆分为多行:

select id, sku, price
  from items
 where item_size in (select column_value
                       from apex_string.split(:P5_SELECTED_SIZES,'|'))

在 PL/SQL 中,可以用 APEX 内置字符串列表类型 APEX_T_VARCHAR2 接收结果:

declare
   l_selected_sizes apex_t_varchar2;
begin
   l_selected_sizes := apex_string.split(:P5_SELECTED_SIZES,'|');
   for j in 1..l_selected_sizes.count loop
      -- Use the j-th size code here
      -- l_selected_sizes(j)
   end loop;
end;

复现路径#

  • 前提:创建 Select Many 页面项 P5_SELECTED_SIZES,返回尺码代码,分隔符为 |
  • 位置:商品报表区域 SQL 查询或服务器端 PL/SQL 处理。
  • 动作:先用 IN (:P5_SELECTED_SIZES) 观察空结果,再改用 APEX_STRING.SPLIT
  • 验证点:选择多个尺码,确认报表返回任一所选尺码的商品。
  • 预期结果:每个尺码代码被作为独立字符串参与过滤。

4.5.4 在命名程序单元中使用数据值#

函数、过程等命名程序单元应尽量通过输入参数接收业务逻辑需要的值。这样代码更可复用,页面调用时再把页面项值作为参数传入。

如果复用性不是重点,也可以使用 APEX_SESSION_STATE 包中的 GET_TYPE() 例程按名称读取页面项、应用项或内置替换字符串。读取字符串时,V()APEX_SESSION_STATE.GET_VARCHAR2() 等价,只是更短。

复现路径#

  • 前提:有一段准备从页面处理抽到数据库包中的业务逻辑。
  • 位置:数据库函数/过程、APEX 页面处理调用处。
  • 动作:优先把 P5_JOBAPP_USER 等依赖设计为参数;只有在确实绑定 APEX 会话状态时才按名称读取。
  • 验证点:同一程序单元能被不同页面调用,且不硬编码页面项名称。
  • 预期结果:业务逻辑和页面上下文解耦,测试与复用成本降低。

4.5.5 配置 Items to Submit 与 Items to Return#

只要区域数据源查询或 WHERE 条件把页面项作为绑定变量引用,就要把页面项名称写入该区域的 Page Items to Submit 属性。这里写名称本身,不写冒号;多个名称用逗号分隔。

如果忘记配置,被引用的绑定变量可能在服务器端意外为 NULL,导致查询无结果或业务逻辑走错分支。

同样,在 Execute Server-side Code 动态操作中引用页面项或网格列时,要正确配置 Items to SubmitItems to Return。遗漏这些属性可能产生 No Data Found 错误,或者服务器端计算出的新值没有回到浏览器,使页面看起来没有按预期更新。

复现路径#

  • 前提:区域查询或动态操作引用了 :P5_DEPTNO:P5_JOB 或网格列绑定变量。
  • 位置:区域属性 Page Items to Submit,或动态操作 Execute Server-side CodeItems to Submit/Items to Return
  • 动作:把依赖的输入项写入 Submit,把服务器端会修改并需要回显到浏览器的项写入 Return。
  • 验证点:打开浏览器开发者工具或 APEX Debug,确认 Ajax 请求提交了所需项值,响应中返回了需要更新的项。
  • 预期结果:服务器端代码读到正确输入,页面端能看到服务器计算后的新值。