APEX实战第3篇:如何完善项目基础功能

上一篇《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这玩意儿,确实是蛮好玩儿的一个低码平台,请继续保持关注,后续还将有更多内容分享。

相关推荐
AlfredZhao10 天前
APEX实战第2篇:构建自己第一个APEX程序
apex
AlfredZhao1 个月前
APEX实战第1篇:本地部署拥有AI能力的APEX
ai·apex·23ai·deepseek·ords
AlfredZhao1 个月前
Oracle DBA末日or重生?不会APEX=淘汰!
apex
王小小鸭1 年前
存档&改造【07】多表查询和可操控对象的存储
oracle·apex·pl/sql
王小小鸭1 年前
存档&改造【06】Apex-Fancy-Tree-Select花式树的使用&误删页数据还原(根据时间节点导出导入)
oracle·apex·pl/sql
Channing Lewis1 年前
salesforce的按钮执行js代码如何链接到apex代码
javascript·apex·salesforce
王小小鸭1 年前
存档&改造【05】通过视图实现多表联查&理清级联层级关系&对字段的唯一约束
oracle·apex·pl/sql
王小小鸭1 年前
存档&改造【04】二维码操作入口设置细节&自动刷新设置后的交互式网格
数据库·oracle·apex
镰刀韭菜2 年前
【分布式训练】基于Pytorch的分布式数据并行训练
pytorch·分布式训练·distributed·apex·混合精度训练·分布式机器学习·horovod