搜索数据是企业应用的基础能力。Oracle APEX 提供多种原生组件,可以按任务场景组合出合适的搜索体验:需要按多个分类条件逐步缩小结果时,用分面搜索(Faceted Search);需要把筛选能力压缩到一个更小的界面里时,用智能筛选器(Smart Filters);需要跨多个来源统一呈现结果时,用搜索区域(Search Region)和搜索配置(Search Configuration);如果要完全控制用户输入条件的方式,则可以用页面项构建查询示例(Query by Example,QBE)。
6 增强最终用户搜索#
本章围绕“用户如何找到正确数据”展开。APEX 的搜索能力不是单一控件,而是一组互补模式:分面搜索适合把候选集逐步收窄;智能筛选器适合空间紧张、结果区需要更大宽度的页面;搜索区域适合把多个业务对象的结果合并成统一列表;页面项过滤则适合开发者自己定义每一个查询条件。
- 分面搜索:用户从一组组分面(Facet)中选择条件,结果区域自动刷新。
- 智能筛选器:用一个搜索框承载文本搜索、筛选器选择和筛选 Chip,适合移动端或顶部区域。
- 统一搜索:通过共享组件中的搜索配置描述各数据源,再由搜索区域统一呈现。
- 查询示例:用普通页面项收集条件,并在其他区域的 SQL 查询中引用这些值。
来源:Oracle APEX 26.1 文档:Enhancing Search for End-Users
6.1 使用分面筛选缩小搜索范围#
分面搜索区域(Faceted Search Region)让用户通过多个分面组选择条件。每个分面代表一个值得筛选的业务特征,例如职位、部门、薪资范围、日期区间或类别。熟悉电商筛选的人会很容易理解这种交互:就像按内存、屏幕尺寸、价格区间筛选笔记本电脑一样,APEX 应用也可以用同样方式筛选任何业务数据。
典型布局是使用 Left Side Column 页面模板,把分面搜索区域放在 Left Column 插槽,把被过滤的结果区域放在 Body 中。结果区域可以是经典报表(Classic Report)、卡片(Cards)、地图(Map)、内容行(Content Row)、支持分面的模板组件或自定义插件。用户每次选择或取消条件时,结果会自动刷新。
来源:Oracle APEX 26.1 文档:Narrowing a Search Using Facet Filters
6.1.1 定义分面#
每个分面至少要配置 Label、Type 和用于过滤的 Database Column。在页面设计器中,分面看起来像页面项,命名也通常类似 P23_JOB;运行时它的值就是最终用户在对应分面组里选择的条件。
对于 Checkbox Group、Radio Group 和 Select List 类型的分面,需要提供值列表(List of Values)。配置方式与普通列表型页面项相同,但多了一个实用选项:Distinct Values。启用后,APEX 会根据当前筛选条件下仍然匹配的行,从底层列中计算不同值作为可选项。
来源:Oracle APEX 26.1 文档:Defining Your Facets
6.1.2 配置范围分面#
范围分面(Range Facet)要求源列的数据类型是 NUMBER、DATE 或 TIMESTAMP。每个范围选项的返回值用竖线 | 分隔下界和上界,格式为 LowValue|HighValue。如果省略其中一端,就表示该方向不设边界。例如显示值 <900 可以对应返回值 |900,表示没有下界、上界为 900。
显示值可以使用替换字符串 #G# 表示千位分隔符,使用 #D# 表示小数分隔符。日期和时间戳范围要使用规范格式 YYYYMMDDHH24MISS。例如“Before 2020”可以写成 |20191231235959;“2020 to 2024”可以写成 20200101000000|20241231235959。
来源:Oracle APEX 26.1 文档:Configuring Range Facets
6.1.3 多选条件的行为#
当用户在 Checkbox Group 或 Range 分面中选择多个值时,APEX 会返回匹配任意一个所选值的行。换句话说,同一个多选分面内部通常是 OR 关系。官方示例中,薪资范围同时选中 900-1300 和 2000-2500,结果会包含薪资落在任一范围内的员工。
来源:Oracle APEX 26.1 文档:Behavior of Multiple Selections
6.1.4 计数、依赖关系和图表#
分面可以显示每个选项当前会匹配多少行,并可按匹配数量排序。对于数量为零的选项,可以选择隐藏,以免用户继续选择没有结果的条件。对于 Checkbox Group 和 Radio Group 分面,还可以把选项按匹配数量从高到低排列。
分面之间也可以建立依赖关系:一个分面只有在用户先选择另一个分面的值之后才显示。对于适合观察分布的分面,还可以启用图表视图,让用户通过饼图或条形图理解当前结果在各个分面值上的分布。
来源:Oracle APEX 26.1 文档:Counts, Dependency, and Charts
6.1.5 处理多值列#
如果某个被过滤的列在一行中可能存储多个值,可以把该分面的 Multiple Values 属性设置为 Delimiter 或 JSON Array。启用后会出现额外设置,例如 Trim Whitespace,用于移除值开头和结尾的空格、制表符或换行符。
Filter Combination 决定多值列如何与用户选择的筛选项匹配:OR (Union) 表示包含任意一个所选值即可匹配;AND (Intersect) 表示必须同时包含所有所选值才匹配。假设 EMP.INTERESTS 用冒号保存多个兴趣:
Art:Animation:Urban Sketching:Math如果 P23_INTERESTS 分面使用冒号作为分隔符,并把组合方式设为 AND (Intersect),那么当用户同时选择 Animation 和 Math 时,这一行会匹配;只包含 Art 或只包含 Math 的员工则不会匹配。
来源:Oracle APEX 26.1 文档:Working with Multi-Value Columns
6.1.6 在“添加筛选器”对话框中显示分面#
配置分面时,可以在属性编辑器的 Appearance 区域决定它是直接内联显示,还是放进 Add Filter 对话框。页面上有大量可选分面时,对话框更适合承载低频筛选条件,避免左侧区域过长。
运行时,用户点击 Add Filter 按钮打开模态对话框,通过顶部的 Select One 项查找要使用的分面,选择筛选值后点击 Apply 应用条件并关闭对话框。
来源:Oracle APEX 26.1 文档:Displaying Facets in Add Filter Dialog
6.1.7 跨列查找文本#
每个分面搜索区域默认都有一个 Search 分面,用于跨多个列查找文本。它可以显示在分面区域内部,也可以引用页面其他位置的页面项。要控制搜索范围,在该 Search 分面的 Source 组中设置 Database Columns,用逗号分隔列名。
用户输入一个或多个词并按 Enter 后,APEX 会查找配置列中包含任一搜索词的行。如果页面上还应用了其他分面条件,则结果必须同时满足这些筛选条件。官方示例中,用户输入 acc 和 cl,匹配结果包括员工名 CLARK、部门列 ACCOUNTING,以及职位列 CLERK 等。
来源:Oracle APEX 26.1 文档:Finding Text Across Columns
6.1.8 显示筛选 Chip 和总行数#
当页面上有很多内联分面,或者部分分面藏在 Add Filter 对话框中时,用户需要快速知道当前哪些条件已经生效。把分面搜索区域的 Show Current Facets 设置为 Yes,即可在分面区域顶部显示筛选 Chip。每个 Chip 会显示分面标签、所选显示值,并带有可移除单个条件的 x 图标;Clear All 链接可一次清空全部条件。
如果左侧栏太窄,更常见的做法是在正文中放一个静态内容区域,把它的 HTML DOM ID 设为例如 active_facets,再把 Show Current Facets 设为 Selector,并在 Current Facets Selector 中填写 #active_facets。同理,Show Total Row Count 可以设为 Yes,也可以设为 Selector 并填写类似 #total_count_area 的选择器,把总行数显示到指定静态区域中。
来源:Oracle APEX 26.1 文档:Showing Filter Chips and Total Row Count
6.2 用智能筛选器为结果区腾出空间#
智能筛选器区域(Smart Filters Region)提供与分面搜索类似的筛选能力,但界面更紧凑。它适合屏幕空间有限,或者搜索结果需要占据整页宽度的页面。智能筛选器可以过滤经典报表、卡片、地图、内容行、支持分面的模板组件和自定义插件。默认外观接近一个单独搜索框。
来源:Oracle APEX 26.1 文档:More Space for Results with Smart Filters
6.2.1 定义筛选器#
每个智能筛选器也要配置 Type、Label 和用于过滤的 Database Column。筛选器名称看起来像页面项名称,运行时值也反映用户的选择。对于 Checkbox Group 和 Radio Group 类型,需要定义值列表;同样可以使用 Distinct Values,由 APEX 基于当前匹配行计算可选值。
范围筛选、计数、依赖关系和多值列的配置方式与前一节分面搜索中的分面一致。计算计数时,Checkbox Group 和 Radio Group 筛选器也可以按匹配数量从高到低展示。
来源:Oracle APEX 26.1 文档:Defining Your Filters
6.2.2 智能筛选器的用户体验#
用户进入智能筛选器搜索框时,下拉列表会显示已定义筛选器的显示名称和图标。用户选择某个筛选器后,搜索框中会出现对应 Chip,下拉列表改为显示该筛选器的可选值。如果该筛选器允许多选,用户可以继续选择多个值;每次选择后,被过滤的结果区域都会立即刷新。
一旦某个筛选器投入使用,它的 Chip 会留在搜索框中表示已应用条件,下拉列表则显示剩余可用筛选器。用户可以回到该 Chip 修改选择,也可以点击 Chip 上的 x 移除它。多个筛选器可以组合使用,例如再选择 Manager 并选中 BLAKE 后,结果会刷新为 BLAKE 管理且薪资位于已选范围内的员工。
用户也可以直接在搜索框中输入一个或多个词,在配置的搜索列中查找文本。搜索列由 Search 筛选器的 Database Columns 属性控制。输入过程中,下拉列表还会显示匹配的筛选选项;例如输入 cl 时,可能出现 Job: CLERK 和 Manager: CLARK。用户可以继续输入并按 Enter 搜索,也可以直接选择建议的筛选选项。
来源:Oracle APEX 26.1 文档:Smart Filters User Experience
6.2.3 配置建议 Chip#
当用户还没有应用任何筛选条件时,智能筛选器可以显示建议 Chip,引导用户从常用或热门条件开始。除必需的 Search 筛选器以外,每个额外筛选器都可以通过 Suggestions > Type 控制建议行为。
- Dynamic:显示第一个可见筛选值;如果该筛选器计算计数,则显示出现频率最高的选项,否则显示排序后的第一个选项。
- Static Values:使用硬编码的建议值。
- SQL Query:用自定义查询决定建议值,例如根据当前用户
:APP_USER或其他数据规则生成建议。 - None:该筛选器不显示建议。
无论哪种方式,建议值都必须是该筛选器值列表中定义的返回值。区域级 Maximum Suggestion Chips 属性可以限制建议 Chip 数量;留空时最多显示 5 个,每个启用建议的筛选器一个。若某个场景中建议没有价值,可以把该属性设为 0 完全关闭建议。
来源:Oracle APEX 26.1 文档:Configuring Suggestions
6.2.4 为搜索结果腾出更多空间#
如果希望把页面主体尽量留给搜索结果,可以把智能筛选器移动到导航栏。做法是把智能筛选器区域的 Parent Region 设置为 No Parent,选择 Before Navigation Bar 插槽,并把 CSS Classes 设为 w100p,让搜索框占据导航栏中的空白空间。这个布局通常搭配 Maximum Suggestion Chips 为 0 使用效果更好。
来源:Oracle APEX 26.1 文档:Even More Space for Search Results
6.3 跨来源统一搜索结果#
用户在 Web 上搜索时,通常是在搜索框里输入几个词,查看顶部结果,再点击有希望的链接。APEX 可以用搜索区域和搜索配置复刻这种体验。搜索配置负责描述每一种可搜索数据源,搜索区域负责把一个或多个搜索配置的结果合并成统一列表。
来源:Oracle APEX 26.1 文档:Unified Search Results Across Sources
6.3.1 定义搜索配置#
每一种需要搜索的来源都应定义一个搜索配置共享组件(Search Configuration)。它标识本地或远程数据源、可搜索列,以及哪些列承担主键、标题、描述、图标等标准角色。为了让用户点击结果后进入详情页,还要在搜索配置上设置链接目标。
根据搜索配置类型,APEX 可以对本地表、查询、APEX 列表项或 REST 数据源执行标准 LIKE 过滤,也可以使用 Oracle Text、Oracle AI Database 26ai Ubiquitous Search 做全文搜索,或使用 Oracle AI Database 26ai Vector Search 做语义相似度搜索。官方 Employees 示例使用如下 SQL 作为来源,并把 ENAME、JOB、DNAME、EMPNO 和 WORKS_FOR_ENAME 作为可搜索列:
select e.empno,
e.ename,
m.ename as works_for_ename,
d.dname,
e.job,
case e.job
when 'PRESIDENT' then 'fa-badgerine'
when 'ANALYST' then 'fa-line-chart'
when 'CLERK' then 'fa-user-headset'
when 'SALESMAN' then 'fa-badge-dollar'
when 'MANAGER' then 'fa-user'
end as icon
from emp e
join dept d
on e.deptno = d.deptno
left join emp m
on m.empno = e.mgr配置中把 EMPNO 映射为主键,把 ENAME 映射为标题,把 JOB 映射为副标题,把 DNAME 映射为描述,并用 EMPNO 显示徽章。随后在 Icon and Display 选项卡指定图标名来自 ICON 列,再配置链接信息:用户点击 Employees 来源的搜索结果时,导航到第 4 页的员工编辑模态页,并把结果行的 EMPNO 传给 P4_EMPNO。
同样方式也可以为 Departments 定义搜索配置:直接基于 DEPT 表,用 fa-building-o 作为每行图标,映射相关列,并设置跳转到部门编辑页的链接。
来源:Oracle APEX 26.1 文档:Defining a Search Configuration
6.3.2 使用搜索配置#
在页面中添加搜索区域后,可以把一个或多个搜索配置作为搜索来源加入该区域。默认情况下,搜索区域会在结果区域上方显示内置搜索框。为了获得更灵活的布局,也可以自己创建一个页面项,例如 P27_SEARCH,把它放在希望的位置,然后在搜索区域的 Search Page Item 属性中引用它。
搜索区域还可以控制结果排序、分页行为,以及是否使用自定义布局。启用 Custom Layout 后,结果模板可以使用条件模板指令,也可以引用 TITLE、SUBTITLE、DESCRIPTION、ICON、LINK 等替换值。完整可用列表以页面设计器中 Result Row Template 属性的帮助页为准。
运行时,用户输入例如 acc 的搜索词后,Departments 和 Employees 两个来源中的匹配结果会同时出现在统一结果列表中。点击像 CLARK 这样来自员工来源的结果时,会打开相应员工详情页。
来源:Oracle APEX 26.1 文档:Using Search Configurations
6.3.3 搜索结果格式化选项#
统一搜索结果的外观可以用三种方式调整:为默认搜索结果编写自定义 CSS;在搜索区域上提供自定义 Result Row Template;或者在部分或全部搜索配置上配置 Default Result Row Template。CSS 适合调整间距、字体和颜色;模板方式则提供更完整的结构控制。
如果在搜索配置上定义默认结果行模板,该模板可以引用数据源列名作为替换值,并且会自动用于该搜索配置的结果。若搜索区域自身也配置了结果行模板,则区域级模板会覆盖配置级模板,以便把所有来源格式化为统一外观。
来源:Oracle APEX 26.1 文档:Search Results Formatting Options
6.3.3.1 自定义搜索区域结果行模板#
默认情况下,如果搜索配置没有指定自己的模板,搜索区域会使用内置结果行模板来格式化结果。这个模板把 HTML 标签、&NAME. 替换引用和用于条件输出的 HTML 模板指令组合在一起。例如搜索配置提供了徽章列时,渲染结果会包含显示徽章的标记;如果没有提供,相关标记就会被省略。
要改变所有搜索结果的展示方式,可以在搜索区域上指定 Result Row Template。启用 Custom Layout 后,属性帮助页会列出可以用 &NAME. 语法引用的标准替换字符串,并能查看内置结果行模板作为参考。
来源:Oracle APEX 26.1 文档:Custom Search Region Row Template
6.3.3.2 自定义搜索配置模板#
如果只想为某个搜索配置的结果使用特殊格式,可以在该共享组件编辑页的 Icon and Display 区域设置 Default Result Row Template。模板中可以使用 HTML 标签、数据源列的 &COLUMNNAME. 替换,以及用于条件格式化的 HTML 模板指令。
APEX 会使用搜索配置自己的默认结果行模板,除非所在搜索区域配置了覆盖所有结果的结果行模板。官方截图与上一节使用同一张帮助页示例,强调模板可用替换字符串和示例模板的位置。
来源:Oracle APEX 26.1 文档:Custom Search Configuration Template
6.4 在其他区域中显示搜索结果#
如果使用搜索配置,可以通过专用的 SEARCH API 在任意区域中呈现搜索结果。但当用户通过分面搜索、智能筛选器、交互式报表或交互式网格过滤数据时,想把同一批过滤结果显示到另一个区域,就需要另一种方式:取得源区域当前过滤后的主键,再让目标区域使用这些主键查询。
来源:Oracle APEX 26.1 文档:Showing Search Results in Other Regions
6.4.1 自定义统一搜索体验#
搜索区域本身会管理搜索框,并用结果行模板显示结果。如果它的界面不符合需求,可以用 APEX_SEARCH 包中的 SEARCH 函数构建自定义搜索体验。该函数接收搜索词和搜索配置静态 ID 列表,返回一个可在 SQL FROM 子句中使用的搜索结果表;使用时用 table() 包住函数调用即可。
下面示例用页面项 P31_SEARCH 中的搜索词,同时搜索静态 ID 为 employees 和 departments 的搜索配置:
select *
from table(
apex_search.search(
p_search_expression => :P31_SEARCH,
p_search_static_ids => apex_t_varchar2('employees',
'departments')))如果外层查询要自己加 ORDER BY,可以额外传入 p_apply_order_bys => 'N',避免函数内部做不必要的排序:
select *
from table(
apex_search.search(
p_search_expression => :P31_SEARCH,
p_search_static_ids => apex_t_varchar2('employees',
'departments'),
p_apply_order_bys => 'N'))
order by title这种方式可以用任意方式收集搜索词,并把结果显示在卡片、地图、图表、经典报表、模板组件或其他 APEX 区域中。返回结果包含与搜索区域 Result Row Template 可用替换值对应的标准列,例如 PRIMARY_KEY_1、PRIMARY_KEY_2、TITLE、SUBTITLE、DESCRIPTION 和 BADGE。搜索配置还可以映射 3 个自定义列,结果中分别以 CUSTOM_01、CUSTOM_02、CUSTOM_03 引用;例如把经纬度映射到 Custom Column 1 和 2 后,可以在地图区域中使用。
来源:Oracle APEX 26.1 文档:Custom Unified Search Experience
6.4.2 在另一个区域中显示过滤后的数据#
要把一个区域的过滤结果显示到另一个区域,可以在目标区域的查询或 WHERE 子句中引用源区域当前结果的主键。每当源区域结果变化时,用动态操作自动刷新目标区域,两个区域就能保持同步。
官方示例中,导航栏中的智能筛选器过滤 EMP 数据,正文左侧是卡片区域,右侧六列放置地图和图表。随着用户缩小卡片结果,地图和图表也展示同一批员工数据。
来源:Oracle APEX 26.1 文档:Showing Filtered Data in Another Region
6.4.2.1 获取过滤后数据的主键#
可以创建一个辅助函数,返回某个过滤区域当前结果的主键列表。下面的 region_pks 函数接收源区域的静态 ID;如果主键列名不是默认的 ID,则通过可选参数 p_pk_column_name 传入列名。函数返回 apex_t_number 管道化列表。
函数内部用 APEX_REGION.OPEN_QUERY_CONTEXT() 打开源区域当前过滤后的查询上下文,再用 APEX_EXEC.NEXT_ROW() 遍历结果,用 APEX_EXEC.GET_NUMBER() 取出主键并逐行管道返回。
create or replace function region_pks(
p_region_static_id in varchar2,
p_pk_column_name in varchar2 default 'ID')
return apex_t_number
pipelined
is
c_page_id constant number := V('APP_PAGE_ID');
l_context apex_exec.t_context;
begin
l_context := apex_region.open_query_context(
p_page_id => c_page_id,
p_region_id => apex_region.get_id(
p_page_id => c_page_id,
p_dom_static_id => p_region_static_id));
while apex_exec.next_row(l_context) loop
pipe row (apex_exec.get_number(l_context, p_pk_column_name));
end loop;
apex_exec.close(l_context);
return;
exception
when no_data_needed then
apex_exec.close(l_context);
return;
end region_pks;来源:Oracle APEX 26.1 文档:Getting Filtered Data Primary Keys
6.4.2.2 使用过滤后数据的主键#
在任意 APEX 区域的数据源查询中,可以使用 SQL 的 table() 操作符读取管道化函数返回的行。下面示例用 WITH 子句把 region_pks() 返回的源区域主键命名为 result_pks,再与 emp 表按 empno 连接。因为左右两边列名相同,using (empno) 比 on emp.empno = result_pks.empno 更简洁。
with result_pks as (
select column_value as empno
from table(region_pks('employees','EMPNO'))
)
select ename,
sal
from result_pks
join emp e using (empno)如果只需要在目标区域的 WHERE 条件中引用主键列表,也可以写成:
empno in (select column_value
from table(region_pks('employees','EMPNO')))来源:Oracle APEX 26.1 文档:Using Filtered Data Primary Keys
6.4.2.3 刷新目标区域#
为了让源区域和目标区域保持同步,需要在源区域上定义动态操作,在源区域结果刷新时触发。对于经典报表区域,事件是 After Refresh;对于卡片区域,使用 Page Change (Cards)。在 True Actions 列表中添加 Refresh 动作,目标指向要联动刷新的地图、图表或其他区域。
来源:Oracle APEX 26.1 文档:Refreshing the Target Region
6.5 使用页面项实现查询示例#
页面项不仅用于数据录入,也适合做查询示例(Query by Example,QBE)条件。用户输入值或从列表中选择值后,同一页面上的另一个区域可以在 WHERE 子句中引用这些页面项值,从而过滤数据。由于 QBE 页面项会频繁改变值,还应考虑关闭默认的未保存更改警告,以免用户误以为自己修改了业务数据。
来源:Oracle APEX 26.1 文档:Using Page Items for Query by Example
6.5.1 使用空值显示文本#
对于 Select List、Radio Group 和 Popup LOV,可以选择在列表中加入一个代表空值的选项,并为它设置自定义标签。这样能帮助用户理解“不选择具体值”在查询条件中的含义。
例如,一个用于按部门过滤员工的可选 Select List 页面项,可以把 Null Display Value 设为 <All>。用户看到这个标签时,就能理解如果不选择具体部门,结果会显示所有部门的员工。
来源:Oracle APEX 26.1 文档:Using Null Display Value
6.5.2 按页面项的值过滤#
任何页面项的值都可以作为同页另一个区域的查询过滤条件。多数页面项类型,包括单选的列表型页面项,只保存一个值,也可能为空。假设 QBE 页面项是名为 P5_SELECTED_DEPARTMENT 的 Select List,可以在另一个区域的 WHERE 子句中直接引用它:
DEPTNO = :P5_SELECTED_DEPARTMENT如果该页面项可以为空,过滤条件需要显式处理空值:
/* Either no department is selected,
or match the selected department */
( :P5_SELECTED_DEPARTMENT IS NULL
OR DEPTNO = :P5_SELECTED_DEPARTMENT)注意:当区域数据源把页面项作为绑定变量引用时,必须把该页面项列入区域的 Page Items to Submit 属性。填写时不要带冒号,多个页面项用逗号分隔。如果忘记设置,区域刷新时绑定变量会被当作空值,查询可能返回空结果。
来源:Oracle APEX 26.1 文档:Filtering on a Page Item's Value
6.5.3 按多选页面项过滤#
当 QBE 页面项允许多选时,过滤通常要用带子查询的 IN 条件。默认情况下,多选页面项的值会把用户所选返回值用冒号 : 拼接保存。假设页面项名为 P5_SELECTED_DEPARTMENTS,可使用 APEX_STRING.SPLIT() 拆分后过滤:
DEPTNO IN (SELECT COLUMN_VALUE
FROM APEX_STRING.SPLIT(:P5_SELECTED_DEPARTMENTS,':'))如果使用了不同分隔符,把它作为 APEX_STRING.SPLIT() 的第二个参数传入即可。如果该多选页面项是可选条件,也要处理空值:
/* Either no department is selected,
or match the selected departments */
( :P5_SELECTED_DEPARTMENTS IS NULL
OR DEPTNO IN (SELECT COLUMN_VALUE
FROM APEX_STRING.SPLIT(:P5_SELECTED_DEPARTMENTS,':')))注意:这里同样要把 P5_SELECTED_DEPARTMENTS 加入目标区域的 Page Items to Submit,否则刷新时绑定变量可能为空。
来源:Oracle APEX 26.1 文档:Filtering on a Multi-Selection Page Item
6.5.4 查询示例的最佳方式#
QBE 的 WHERE 条件有两种常见写法。一种更容易理解,另一种性能更好。简单写法是先判断页面项值是否为空,再用 OR 条件决定是否按列过滤:
/* Either no department is selected,
or match the selected department */
( :P5_SELECTED_DEPARTMENT IS NULL
OR DEPTNO = :P5_SELECTED_DEPARTMENT)数据库首次处理查询时会参考绑定变量值来选择索引和执行计划,并在后续执行同一 SQL 语句时复用该计划,即使绑定变量值已经变化。因此,包含大量这种 OR 条件的通用 SQL 不一定总能使用最适合当前过滤条件的索引。
性能更好的策略是:只有当对应页面项不为空时,才把该过滤条件加入 SQL;为空时则不加入该谓词。APEX 的区域数据源可以选择 Function Body Returning SQL,用 PL/SQL 动态拼接并返回最终 SQL。下面示例只在 P5_SELECTED_DEPARTMENT 不为空时加入部门过滤:
return q'~
SELECT EMPNO, ENAME, SAL, DEPTNO, HIREDATE
FROM EMP
WHERE 1=1
~'
|| CASE WHEN :P5_SELECTED_DEPARTMENT IS NOT NULL THEN
q'~
AND DEPTNO = :P5_SELECTED_DEPARTMENT
~' END;这里使用 PL/SQL 的多行字符串语法 q'~ ... ~',可读性更好,也避免手工转义单引号。双竖线 || 是 Oracle SQL 的字符串连接运算符。CASE 表达式在没有满足条件时返回空值,而字符串连接空值不会改变原字符串。
运行时,APEX 引擎会用一个外层函数包住你在属性编辑器中输入的函数体:
function x
return varchar2
is
begin
/* Your Function Body Here */
end x;当 P5_SELECTED_DEPARTMENT 为空时,数据库实际看到的查询是:
SELECT EMPNO, ENAME, SAL, DEPTNO, HIREDATE
FROM EMP
WHERE 1=1当它有值时,数据库执行的查询是:
SELECT EMPNO, ENAME, SAL, DEPTNO, HIREDATE
FROM EMP
WHERE 1=1
AND DEPTNO = :P5_SELECTED_DEPARTMENT同样方式可以按多个页面项有选择地添加多个条件。使用恒真的 WHERE 1=1 可以保证无论追加多少条件,SQL 语法都成立,并且每个条件都能统一以 AND 开头:
return q'~
SELECT EMPNO, ENAME, SAL, DEPTNO, HIREDATE
FROM EMP
WHERE 1=1
~'
|| CASE WHEN :P5_SELECTED_DEPARTMENT IS NOT NULL THEN
q'~
AND DEPTNO = :P5_SELECTED_DEPARTMENT
~' END
|| CASE WHEN :P5_ENAME_CONTAINS IS NOT NULL THEN
q'~
AND INSTR(UPPER(ENAME), UPPER(:P5_ENAME_CONTAINS)) > 0
~' END
|| CASE WHEN :P5_MAX_SALARY IS NOT NULL THEN
q'~
AND SAL <= :P5_MAX_SALARY
~' END;为了提升可维护性,可以把 SQL 构建逻辑放进 PL/SQL 包函数。假设有一个 UTILS 包,其中定义了 p5_employees_query,区域的 Function Body Returning SQL 就可以简化为:
return utils.p5_employees_query(:P5_SELECTED_DEPARTMENT,
:P5_ENAME_CONTAINS,
:P5_MAX_SALARY);包函数本身可以写成:
function p5_employees_query(
p_selected_deptno in number,
p_ename_contains in varchar2,
p_max_salary in number)
return varchar2
is
begin
return q'~
SELECT EMPNO, ENAME, SAL, DEPTNO, HIREDATE
FROM EMP
WHERE 1=1
~'
|| CASE WHEN p_selected_deptno IS NOT NULL THEN
q'~
AND DEPTNO = :P5_SELECTED_DEPARTMENT
~' END
|| CASE WHEN p_ename_contains IS NOT NULL THEN
q'~
AND INSTR(UPPER(ENAME), UPPER(:P5_ENAME_CONTAINS)) > 0
~' END
|| CASE WHEN p_max_salary IS NOT NULL THEN
q'~
AND SAL <= :P5_MAX_SALARY
~' END;
end p5_employees_query;注意:只要函数体返回的 SQL 或 WHERE 子句中可能以绑定变量形式引用页面项,就要把这些页面项列入区域的 Page Items to Submit。另外,Page Designer 检查 Function Body Returning SQL 的语法时,所有绑定变量都会按空值求值,因此函数在这种情况下也必须返回语法有效的 SELECT 语句。
来源:Oracle APEX 26.1 文档:Optimal Approach to Query by Example
6.5.5 关闭未保存更改警告#
QBE 页面项本来就会随着用户筛选而改变值。如果用户只是筛选结果,却在离开页面时看到“有未保存更改”的警告,容易误以为自己修改了需要保存的业务数据。对于用作查询条件的页面项,应根据场景关闭 Warn on Unsaved Changes。
该设置既可以在页面级统一配置,也可以按具体页面项单独配置。判断原则是:如果页面项代表的是临时筛选条件,而不是需要提交保存的数据,就不应让它触发离页未保存警告。