上一篇《APEX实战第2篇:构建自己第一个APEX程序》虽然有了程序,但实在是太单薄!
本篇将会介绍一些数据库的基础知识,演示如何通过函数、触发器、存储过程、视图等来完善项目的一些基础功能。
没有编程经验也完全没关系,笔者其实也从来都没做过程序员,但可以借助APEX结合一些数据库基础知识,就能让我们也能轻松构建属于自己的应用程序。
比如这里举例,我想构建一个学习平台,所谓活到老学到老,这里就叫小鲸鱼终身学习平台
吧,嗯,让它看起来像个真正的项目,这样介绍起来也会有趣些。
项目名称:小鲸鱼终身学习平台
嗯。。做戏做全套,干脆正式一点,把上篇的名字都改掉,重定义下:
英文:Little Whale Lifelong Learning Platform
Logo:WhaleStudy
- 1.构建验证用户登录的函数
- 2.利用触发器自动维护历史记录
- 3.使用存储过程处理重复数据问题
- 4.巧用视图构建DIY数据
1.构建验证用户登录的函数
基础功能先搞简单些,我只需要实现不同用户登录系统,能且只能看到自己的学习进度,可以通过登录用户加以判断。
举实际例子吧,我这里先假设已有3个用户:duoduo、manman、test;
- 多多小朋友 username=duoduo
- 满满小朋友 username=manman
- 测试人员兼管理员 username=test
要求用户登录只能看到自己的数据,管理员登录可以有权限看到一些额外的管理菜单和子项。
首先构建一张用户表,就三列,用户、密码、是否管理员:
sql
--新建表 T_USERS
create table T_USERS (
username varchar2(30) primary key,
password varchar2(50) not null,
is_admin number default 0
);
然后创建一个函数,专门用于验证用户登录:
sql
--新建函数 F_LOGIN
create or replace function F_LOGIN(
p_username in varchar2,
p_password in varchar2
) return boolean is
l_cnt number;
l_result boolean;
begin
--判断用户密码
select count(*) into l_cnt from t_users
where username = p_username
and password = p_password;
--判断
IF l_cnt=1 THEN
l_result := true;
--htp.p('Welcome,' || p_username);
ELSE
l_result := false;
--htp.p('Error!!!' || p_username);
END IF;
return l_result;
--异常处理
exception
WHEN OTHERS THEN
RETURN false;
end;
/
手工测试:
sql
--ORACLE 23ai可以不用再写from dual,当然,写也不会报错,还能更好向下兼容,看个人习惯
--正确返回True
select f_login('test','test');
--错误返回False
select f_login('test','123');
在APEX界面,找到你的程序进入到验证方案,比如:Application 102 -> Shared Components -> Authentication Schemes,新建一个账号密码登录
的验证方案,编辑时选择 Authentication Function Name 指定为上面创建的函数名。
不过登录测试还存在问题,因为我这里用户表中都是小写的用户名,但是网上搜索发现APEX登录界面会默认自动转为大写,这是因为账号大小写敏感设置问题。
这点参考了网上公开资料,可以这样修改,亲测有效,在登录页面Login部分插入一段PL/SQL Code:
sql
apex_authentication.login(
p_username => :P9999_USERNAME,
p_password => :P9999_PASSWORD,
p_uppercase_username => FALSE);
再次测试使用小写账号登录成功。
OK,登录搞定,也学会了简单函数的使用。
关于只能看到自己的数据?
- 可以通过配置APEX界面,数据库where条件为:
username = :APP_USER
,这里的:APP_USER
就是当前登录的用户变量。
关于管理员登录可以有权限看到一些额外的管理菜单和子项?
- 可以通过
Application 102 -> Shared Components -> Authorization Schemes
配置一个管理权限,Scheme Type
选择Exists SQL Query
,SQL Query
内容为:select 1 from t_users where username = :APP_USER and IS_ADMIN = 1
,然后在导航菜单等地方就可以选择这个管理权限。
2.利用触发器自动维护历史记录
我这里设计一个稍复杂的场景,就是之前的T_CURRENT表,在进行更新修改都没有任何记录,无法分析历史数据,所以现在我就需要搞一张历史表专门用于存储历史信息,这里就通过新建一个触发器来实现这个功能,如下:
sql
create or replace TRIGGER TRI_T_CURRENT
AFTER INSERT OR UPDATE OR DELETE ON "T_CURRENT"
FOR EACH ROW
DECLARE
v_current_date DATE := SYSDATE; -- 当前系统日期
BEGIN
IF INSERTING THEN
-- 插入操作,将插入的数据和当前日期插入到T_HISTORY表
INSERT INTO t_history (type, week, day, content, username, history_date)
VALUES (:new.type, :new.week, :new.day, :new.content, :new.username, v_current_date);
ELSIF UPDATING THEN
-- 更新操作,先尝试更新已有的记录,如果找不到,则插入新记录
-- 我这样设计,是因为想随时添加新的content内容,而不让历史表记录零碎信息
MERGE INTO t_history h
USING (SELECT :new.type AS type, :new.week AS week, :new.day AS day, :new.username AS username FROM dual) src
ON (h.type = src.type AND h.week = src.week AND h.day = src.day AND h.username = src.username)
WHEN MATCHED THEN
UPDATE SET h.content = :new.content, h.history_date = v_current_date
WHEN NOT MATCHED THEN
INSERT (type, week, day, content, username, history_date)
VALUES (:new.type, :new.week, :new.day, :new.content, :new.username, v_current_date);
ELSIF DELETING THEN
-- 删除操作,将被删除的数据和当前日期插入到T_HISTORY表
INSERT INTO t_history (type, week, day, content, username, history_date)
VALUES (:old.type, :old.week, :old.day, :old.content, :old.username, v_current_date);
END IF;
END;
/
其实,我主要用到的场景就是更新,只不过更新的要求稍多一点,因为我不想太多垃圾记录存在,详见上面代码注释部分说明。
3.使用存储过程处理重复数据问题
存储过程,用于做些啥呢,容笔者现编一下应用场景。。干脆就用于删除历史遗留的数据重复问题吧:
sql
--新建存储过程 P_CLEAN_DUP_HISTORY
CREATE OR REPLACE PROCEDURE P_CLEAN_DUP_HISTORY
IS
BEGIN
DELETE FROM t_history h
WHERE h.history_date < (
SELECT MAX(h2.history_date)
FROM t_history h2
WHERE h.type = h2.type
AND h.week = h2.week
AND h.day = h2.day
AND h.username = h2.username
);
COMMIT;
END;
/
注意,我这里定义的重复数据,是根据我这个程序的业务场景来决定,我认为同一用户,在同一天(history_date),同一课程类型(type)、相同分片(week和day都一样),只能有一条,如果存在多条,一定是之前的记录CONTENT内容不完整,可以删除掉这样的垃圾条目,只保留最新的完整记录行。
额,自己说起来都感觉好绕,如果不理解可以多读几遍。。就是类似上面这种重复数据,还不理解也没关系,这里主要就是刷一下存储过程的存在感。
这样执行存储过程:
sql
begin
P_CLEAN_DUP_HISTORY;
end;
之后再去查数据发现已实现这个去重数据的功能。
4.巧用视图构建DIY数据
那就搞一张视图来专门展示CONTENT中的内容吧!
sql
create or replace view V_CONTENT as
select content from T_CURRENT
where content is not null;
嗯,看起来貌似有点没太大必要哈,是因为这个场景太简单了,我就是想展示一下可以这么玩,等以后遇到更复杂的场景就能看出巧用视图的优势了。
注意,细心的小伙伴应该已经发现了我这里的命名规范:
- 所有表以
T_
开头 - 所有函数以
F_
开头 - 所有过程以
P_
开头 - 所有视图以
V_
开头 - 所有触发器以
TRI_
开头
...
职业病又犯了。。之前做DBA时还总是去给开发人员培训,让他们遵守一些开发规范,好利于排查维护。
当然你也可以构建你自己的一套命名规范,只要养成这个好习惯,以后的管理维护工作就会多一缕轻松愉快。
回到正题,是不是做过ORACLE DBA的小伙伴会觉得APEX这玩意儿简直是太好玩了,终于能比较容易的把一些内功给输出到前端了。
所以并不是做广告,笔者真觉得APEX这玩意儿,确实是蛮好玩儿的一个低码平台,请继续保持关注,后续还将有更多内容分享。