企业应用离不开数据。Oracle APEX 让开发者能够高效处理各种数据源;虽然 APEX 也能轻松集成远程数据,但多数 APEX 应用仍主要使用本地数据库表中的数据。
2.1 数据库概念速览#
Oracle APEX 引擎和 App Builder(应用构建器)运行在 Oracle Database 内部。当应用访问同一个数据库中的数据时,开发过程和运行时页面都能获得本地访问的零网络延迟优势。访问另一套 Oracle 数据库或 REST API 等远程数据源时,需要经过网络请求,这一点与本地数据不同。
在 App Builder 的 Object Browser(对象浏览器)中,可以创建和维护应用需要的本地数据库对象。除了用于保存业务数据的表之外,还可以创建视图来按名称复用特定查询,定义触发器来执行业务规则,把自定义代码组织到包中,等等。下图展示了 Object Browser 的首页,左侧导航中可以看到表、视图、包和触发器等对象名称。
2.1.1 数据库表#
应用数据通常组织在表中。每张表代表一个业务实体,例如员工或部门;表中的每一列对应实体的一个属性,例如员工入职日期或部门所在地。每一列都有数据类型,用于规定它保存的是文本、数字、日期还是其他类型的信息。
表的 primary key(主键)列用于唯一标识每一行。主键也是建立表间关系的基础,可以把一张表中的某行数据关联到另一张表中的相关行。foreign key(外键)是一张表中的列,它引用另一张表的主键值,用来保证数据一致性并维护引用完整性。
下图展示了员工和部门两张表。EMP 表的 EMPNO 列是主键,DEPT 表的 DEPTNO 列也是主键,界面中都以钥匙图标标识。EMP 表中的 DEPTNO 列是外键,它引用 DEPT 表的主键,用来说明每名员工所属的部门。
APEX Builder 的 Quick SQL 编辑器和 Create Data Model Using AI(使用 AI 创建数据模型)向导可以帮助你快速创建一组初始表;而 Object Browser 则适合在应用演进过程中持续维护这些表。
官方来源:Database Tables
2.1.2 结构化查询语言 SQL#
构建 Oracle APEX 应用时,你会使用行业标准的 Structured Query Language(SQL,结构化查询语言),用简单的声明式语法说明要处理哪些数据。
SQL 关注“要读取或修改什么数据”,而不要求你说明数据库内部“如何执行”。虽然有人会逐字母读出 SQL,多数人会把它读作类似英文单词“sequel”的发音。
扎实基础,黄金标准。 SQL 的发展始于 20 世纪 70 年代初,由全球可信 IT 公司中的大量专家共同推动,逐步形成了声明式数据语言的黄金标准。SQL 功能完整,但学习时不必一次掌握全部内容;可以先从基础语句开始,在需求增加时再逐步学习新的命令。
官方来源:Structured Query Language (SQL)
2.1.2.1 基本 SQL 语句#
掌握一小组基本操作,就能在 Oracle APEX 应用开发中高效使用 SQL。
SELECT 语句用于检索数据,以便在页面上显示,或在自定义业务逻辑中引用。FROM 子句指定要读取的表。当一张表包含外键列时,JOIN 子句可以同时读取相关表中的数据。由于 SELECT 命令是在向数据库请求信息,因此它也常被称为 query(查询),用于回答类似“哪些员工符合这些条件?”这样的问题。
其他 SQL 语句可以向表中 INSERT 新行,按需要 UPDATE 现有行,或 DELETE 不再需要的行。如果一个逻辑业务事务包含多次修改,要么使用 COMMIT 把这些修改作为一个整体保存,要么使用 ROLLBACK 将它们全部放弃。
SQL 命令可以指定要选择、更新或删除哪些行。WHERE 子句使用筛选表达式识别参与处理的行,ORDER BY 子句对结果排序。下面的 SELECT 语句从相关的 EMP 和 DEPT 表中取回员工姓名、工资以及其所在部门的位置。它只返回工资在 800 到 2450 之间、职位为 CLERK 或 MANAGER 的员工。JOIN ... ON 子句要求用双方的 DEPTNO 值建立员工与部门之间的关系。结果按工资降序排列,也就是工资最高的排在前面。
SELECT emp.ename, emp.sal, dept.loc
FROM emp
JOIN dept ON emp.deptno = dept.deptno
WHERE sal BETWEEN 800 AND 2450
AND job IN ('CLERK','MANAGER')
ORDER BY sal DESC
该查询产生以下结果行:
ENAME SAL LOC
--------- ------- -----------
CLARK 2450 NEW YORK
MILLER 1300 NEW YORK
ADAMS 1100 DALLAS
JAMES 950 CHICAGO
SMITH 800 DALLAS
APEX 的原生组件可以根据表名和页面引用的列集合自动生成 SQL 语句。不过,在编写自定义业务逻辑,或为页面某一部分精细调整数据时,SQL 基础会成为日常开发中的关键能力。无论 SQL 是你编写的还是组件生成的,APEX 都会按最终用户需求自动补充筛选、排序、分页和聚合等能力。下图中的 SQL Workshop(SQL 工作坊)里的 SQL Commands(SQL 命令)编辑器,是实验 SQL 的简易沙盒。
官方来源:Basic SQL Statements
2.1.2.2 随身 SQL 导师#
APEX AI Assistant(APEX AI 助手)可以充当你的 SQL 导师。注册 APEX 要使用的生成式 AI 服务后,这个 AI 驱动的设计期助手会出现在 App Builder 的每个代码编辑器中,帮助你按需编写、学习和调试 SQL。
要编写 SQL 时,可以用英文说明需要处理的数据,APEX AI Assistant 会帮助生成完成任务所需的语句。要提升 SQL 理解力时,它也可以回答疑问;在开发过程中遇到 SQL 错误时,它还能建议修正方式。
在 SQL Code Editor 中使用。 除了在 SQL Workshop 的 SQL Commands 页面使用 APEX AI Assistant,还可以在 SQL Code Editor(SQL 代码编辑器)中使用它。这个模态对话框在 App Builder 中随处可用;只要某处可以指定 SQL,APEX AI Assistant 就能一键打开,帮助你加速 SQL 编写和学习。
2.1.3 视图、触发器与包#
表用于保存应用数据,但理解视图、触发器和包如何补充表同样重要。
视图使用 SQL 定义;触发器、过程、函数和包则使用 PL/SQL 编写,也就是 SQL 的过程式语言扩展。如果数据库是 Oracle 26ai,也可以使用 JavaScript。App Builder 中的 Object Browser 可以方便地创建和维护应用需要的表、视图、触发器和包。
官方来源:Database Views, Triggers, and Packages
2.1.3.1 数据库视图#
数据库视图是一个具名 SQL 查询,可以在 APEX 应用中复用,用来简化数据访问。
创建 database view(数据库视图)时,本质上是在给一个可复用的 SELECT 语句命名。视图可以识别适合某个业务场景的精选数据子集,也可以按需要对相关表进行筛选和连接。之后,应用页面和业务逻辑就可以像使用表名一样使用视图名。App Builder 中的 Object Browser 可用于创建和维护视图。
例如,下面的语句使用前面小节中的 SELECT 语句创建一个名为 mid_level_clerks_and_mgrs 的视图:
CREATE OR REPLACE VIEW mid_level_clerks_and_mgrs AS
SELECT emp.ename, emp.sal, dept.loc
FROM emp
JOIN dept ON emp.deptno = dept.deptno
WHERE sal BETWEEN 800 AND 2450
AND job IN ('CLERK','MANAGER')
定义视图后,可以像查询表一样从视图中选择数据,并继续增加额外的筛选和排序:
SELECT ename, sal, loc
FROM mid_level_clerks_and_mgrs
WHERE loc LIKE '%A%'
ORDER BY sal
结果如下:
ENAME SAL LOC
---------- ---------- ----------
SMITH 800 DALLAS
JAMES 950 CHICAGO
ADAMS 1100 DALLAS
官方来源:Database Views
2.1.3.2 数据库触发器#
数据库触发器可以集中验证数据并计算列值,无论哪个应用访问该表,规则都会生效。
可以在表上定义触发器,让自定义业务逻辑在插入、更新或删除表中某行之前或之后执行。如果触发器在操作前触发,就可以在行保存之前执行校验规则,并按需计算列值。触发器可在 App Builder 的 Object Browser 中创建和维护。
例如,下面的触发器会把员工工资四舍五入到最接近的 10 的倍数;如果工资大于 9000,则抛出错误。语法直观:它在 EMP 表每一行插入或更新之前执行。注意它如何使用特殊绑定变量语法 :NEW.column_name 引用 EMP 表列的新值。
CREATE OR REPLACE TRIGGER emp_bef_ins_or_upd
BEFORE INSERT OR UPDATE ON emp FOR EACH ROW
BEGIN
-- If incoming SAL value exceeds 9000, then raise error
IF :NEW.sal > 9000 THEN
raise_application_error(-20001,'Maximum salary is 9000');
END IF;
-- Round to nearest multiple of ten. The -1 means
-- 1 digit to the *left* of the decimal point
:NEW.sal := ROUND(:NEW.sal, -1);
END;
在 EMP 表上创建该触发器后,如果尝试用下面的 SQL 语句把员工 JAMES 的工资设置为 9954,会得到错误:
UPDATE emp
SET sal = 9954
WHERE empno = 7900 /* JAMES */
如预期所示,数据库会返回以下错误,并阻止该语句原本想做的修改:
ORA-20001: Maximum salary is 9000
如果改用类似的 UPDATE 语句把 JAMES 的工资更新为 954,更新会成功。不过再次 SELECT 工资时,会看到触发器已把 954 四舍五入为 950。
官方来源:Database Triggers
2.1.3.3 数据库包#
数据库包包含可用过程和函数的规范。Oracle 建议使用包来组织自定义业务逻辑。
package specification(包规范)列出应用页面、触发器或其他包可以调用的过程和函数,同时定义这些命名程序单元接受的参数名称和数据类型。参数可以让调用方把数据传入函数或过程(IN)、从中接收数据(OUT),或双向传递数据(IN OUT)。任何 IN 参数都可以是必填或可选;如果未显式提供,可选参数会采用定义好的默认值。函数与过程的关键区别是:函数返回一个结果值,而过程不返回结果值。
公共程序单元非常适合为团队成员提供可在页面、工作流或自定义代码中使用的应用功能。调用者只需要调用函数或过程,并提供必需参数值。可以在 App Builder 中通过 SQL Workshop 的 Object Browser 创建和维护包。
例如,下面的 hr_utils 包规范定义了一个过程和一个函数。handle_employer_contribution 过程中的 p_additional_withholding 参数是可选参数,默认值为 0。p_total_contribution 参数通过 OUT 将信息返回给调用者,其他参数则通过 IN 传入输入值。
CREATE OR REPLACE PACKAGE hr_utils AS
---------------------------------------
PROCEDURE handle_employer_contribution(
p_empno IN NUMBER,
p_additional_withholding IN NUMBER DEFAULT 0,
p_total_contribution OUT NUMBER);
---------------------------------------
FUNCTION years_employed(
p_hiredate IN DATE)
RETURN NUMBER;
END hr_utils;
包规范中公开声明的程序单元,需要在对应的 package body(包体)中实现。包体中的自定义业务逻辑可以保持私有。公开/私有的区分很重要:它意味着你可以改变包中过程或函数的实现方式,而不影响使用它的页面或其他程序单元。如果要分发应用,还可以选择 wrap(包装/混淆)包体代码,防止代码被直接阅读。
下面的 hr_utils 包体定义了包规范中过程和函数的行为。注意,包体可以包含其他程序单元,例如 years_ago 辅助函数;这些程序单元不能从包外访问。years_employed 函数会调用这个包私有的 years_ago 函数。
CREATE OR REPLACE PACKAGE BODY hr_utils AS
---------------------------------------
FUNCTION years_ago(
p_date IN DATE)
RETURN NUMBER
IS
BEGIN
RETURN FLOOR(MONTHS_BETWEEN(SYSDATE, p_date) / 12);
END years_ago;
---------------------------------------
PROCEDURE handle_employer_contribution(
p_empno IN NUMBER,
p_additional_withholding IN NUMBER DEFAULT 0,
p_total_contribution OUT NUMBER)
IS
BEGIN
apex_debug.enter('handle_employer_contribution');
-- Interesting contribution code here, eventually
-- assigning a value to p_total_contribution
END handle_employer_contribution;
---------------------------------------
FUNCTION years_employed(
p_hiredate IN DATE)
RETURN number
IS
BEGIN
RETURN years_ago(p_hiredate);
END years_employed;
END hr_utils;
当页面、触发器或另一个包调用 handle_employer_contribution 这样的过程时,需要为 IN 参数提供值,并为 OUT 参数提供接收位置。使用 PL/SQL 命名参数语法,可以按任意顺序传参,每个值的用途都很清楚。
-- Handle the employee's pension contribution
hr_utils.handle_employer_contribution(
p_additional_withholding => 1500,
p_empno => :P71_EMPLOYEE_ID,
p_total_contribution => :P71_TOTAL);
使用 APEX Builder 原生的 Invoke API(调用 API)页面处理或工作流活动,也可以以声明式方式调用包过程或函数。只需要在 Page Designer(页面设计器)或 Workflow Designer(工作流设计器)中配置每个参数的值,就能让技术背景较弱的团队成员也使用包中提供的功能。
官方来源:Database Packages
2.2 使用内置示例数据#
学习 Oracle APEX 时,如果还没有一组可用于实验的现有数据库表,可以先使用内置示例数据。
在 App Builder 中创建新页面时,只需在 Data Source(数据源)处选择 Sample Data(示例数据),如下图所示。
内置示例数据默认使用 Products(产品)。如下图所示,也可以在 Page Designer 的 Property Editor(属性编辑器)中改用 Employees(员工)、Tasks(任务)或 Projects(项目)数据。
2.3 安装示例数据集#
除了使用内置示例数据,还可以安装额外的 sample datasets(示例数据集)。这些数据集通常包含多张围绕某个功能领域相关联的表。
每个示例数据集都包含一张或多张与以下用例相关的示例数据表:
- Countries:国家、人口与首都。
- Customer Orders:客户、门店(含经纬度)、产品与订单。
- EMP / DEPT:经典的
EMP和DEPT表,包含员工与部门。 - HR Data:Oracle Education 使用的人力资源表。
- Health Updates:患者健康状态更新。
- Project Data:项目、里程碑与任务。
- Tasks Spreadsheet:类似电子表格的任务表,包含任务、日期、状态、负责人、成本和预算。
所有示例数据都以英文提供。不过,安装 EMP / DEPT 数据集时,也可以选择阿拉伯语、中文、捷克语、法语、德语、意大利语、日语、韩语、波兰语、俄语或西班牙语。本指南中的示例会尽可能使用 Employees and Departments 示例数据集中的简单 EMP 和 DEPT 表。
官方来源:Installing a Sample Dataset
2.4 从文件导入数据#
APEX Builder 的 Data Workshop(数据工作坊)可以轻松导入 Excel 电子表格,以及 JSON、XML 或 CSV(逗号分隔值)格式的文件。
许多企业应用都是从导入现有 Excel 电子表格开始的。把文件中的数据迁移到集中数据库,可以确保所有用户同时看到并在权限允许时修改同一个事实来源。可以从本机文件加载数据,也可以从 Oracle Cloud Infrastructure(OCI)对象存储桶中的云端文件加载。
Data Workshop 会解析要加载的文件,并自动推断识别到的列应使用的数据类型。可以把选定列装载到现有表中,也可以让 Data Workshop 创建新表并装载数据。下图展示了把包含纽约市公立高中数据的电子表格装载到新表中。Load Data(加载数据)向导会预览文件数据,并允许配置要加载哪些列。
官方来源:Importing Data from a File
2.5 使用 Quick SQL 创建数据模型#
Quick SQL 可以让你用简写方式描述数据模型,从而快速创建表和关系。
应用的 data model(数据模型)是一组用于保存应用所管理信息的表和关系。准备创建新应用时,APEX Builder 的 Quick SQL 编辑器是迭代设计数据表的高效工具。你可以用简写语法逐行输入数据模型中的业务实体,并在每个实体下缩进列出属性和数据类型;表之间的关系则通过外键标识。
假设要构建一个会议管理应用。下面的 Quick SQL 语法定义了 ROOMS、PRESENTERS、SESSIONS、ATTENDEES 和 AGENDAS 五张表。Quick SQL 会自动为每张表创建名为 ID 的主键,因此无需显式列出。NAME 和 TITLE 列用于保存最大长度为 100 个字符的可变长度字符数据(vc100),ROOMS 还包含两个数字列用于保存地图坐标。每个 session(会议场次)都有一个包含时间的 STARTS 日期,以及以分钟为单位的数字型 DURATION。注意,/fk 或 /references 注解用于把某列标识为 foreign key(外键),并说明它引用哪张表。本例中,SESSIONS 表的每行都有 ROOM_ID 表示场次所在房间,PRESENTER_ID 表示主讲人;类似地,AGENDAS 表的每行都有 SESSION_ID 表示会议场次,ATTENDEE_ID 表示希望参加该场次的参会者。
rooms
name vc100
latitude num
longitude num
presenters
name vc100
sessions
title vc100
starts date
duration num
room_id /fk rooms
presenter_id /fk presenters
attendees
name vc100
agendas
session_id /references sessions
attendee_id /references attendees
在 Quick SQL 编辑器中输入并稍作停顿时,Diagram(图表)标签页会立即更新表、列和关系的可视化表示。任何时候都可以查看 SQL 标签页,检查创建新表所需的语句。对数据模型满意后,点击 Review and Run(审阅并运行)即可保存 SQL 脚本并创建表。
官方来源:Creating Data Models with Quick SQL
2.6 使用自然语言创建数据模型#
可以使用自然语言描述应用需要管理哪些数据,从而创建数据模型。
当你知道要构建哪类应用,但需要数据模型建议时,APEX AI Assistant 可以提供帮助。APEX Builder 的 Create Data Model Using AI(使用 AI 创建数据模型)向导会打开一个聊天窗口,你可以在其中说明应用性质。例如,如果要构建会议管理应用,可以输入类似下面的提示词:
用于管理为期一周的 Oracle AI World 大会的应用。大会包含由一名或多名演讲者主讲的会议场次。每个场次只发生一次,在一个房间中按指定开始时间举行,持续时间以分钟计。房间分布在多家酒店。参会者整周预订同一家酒店。他们通过选择自己想参加的每个唯一会议场次和每项晚间活动来创建个人日程。所有表名前缀都使用 AIW。
如下图所示,APEX AI Assistant 会用 Quick SQL 记法返回建议的数据模型,其中包含建议表、列、数据类型和关系。必要时可以继续对话提出调整请求,然后点击 Review Quick SQL(审阅 Quick SQL)在 Quick SQL 图表中检查数据模型。
根据提示词,APEX AI Assistant 会创建如下图所示的数据模型。如果发现遗漏,可以继续编辑 Quick SQL;准备就绪后,点击 Review and Run(审阅并运行)保存脚本并创建表。